/*
 * Copyright (c) 2022 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the 'License');
 * you may not use self file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an 'AS IS' BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
import java.util.stream.Stream;

import static java.lang.Math.ceil;

class MlConstrants {
    public static final int MLINDEX_BYTES_NUM_2 = 2;
    public static final int MLINDEX_BYTES_NUM_3 = 3;
    public static final int MLINDEX_BYTES_NUM_4 = 4;
    public static final double MLINDEX_BYTES_NUM_03 = 0.3;
    public static final double MLINDEX_BYTES_NUM_05 = 0.5;
    public static final double MLINDEX_BYTES_NUM_06745 = 0.6745;
    public static final double MLINDEX_BYTES_NUM_20 = 2.0;
    public static final int MLINDEX_BYTES_NUM_100 = 100;
    public static final int MLINDEX_BYTES_NUM_52 = -52;
    public static final double MLINDEX_BYTES_NUM_0520 = -52.0;
    public static final int MLINDEX_BYTES_NUM_1000 = 1000;
    public static final int MLINDEX_BYTES_NUM_512 = 512;
    public static final int MLINDEX_BYTES_NUM_10 = 10;
    public static final int MLINDEX_BYTES_NUM_50 = 50;
    public static final int MLINDEX_BYTES_NUM_75 = 75;
    public static final double MLINDEX_BYTES_NUM_075 = 0.75;
    public static final double MLINDEX_BYTES_NUM_04375 = -0.4375;
    public static final int MLINDEX_BYTES_NUM_30 = 30;
    public static final double MLINDEX_BYTES_NUM_0964 = 0.964;
    public static final int MLINDEX_BYTES_NUM_002 = -2;
    public static final double MLINDEX_BYTES_NUM_001 = 0.01;
    public static final int MLINDEX_BYTES_NUM_5 = 5;
    public static final int MLINDEX_BYTES_NUM_6 = 6;
    public static final int MLINDEX_BYTES_NUM_020 = 20;
    public static final double MLINDEX_BYTES_NUM_050 = 5.0;
    public static final double MLINDEX_BYTES_NUM_40 = 4.0;
    public static final int MLINDEX_BYTES_NUM_040 = 40;
    public static final int NUM_STIME_CONST = 1000;
    public static final int NUM_TIME_LOOP_CONST = 60;
    public static final int NUM_TIME_LOOP_5 = 5;


    public static MlMatrixDcuTil mlMatrixDcuTil = new MlMatrixDcuTil();
    public static MlMatrixUtilCls mlMatrixUtil = new MlMatrixUtilCls();
    public static MlMatrixDecompositionsCls mlMatrixDecompositions = new MlMatrixDecompositionsCls();
    public static MlArrayUtilsArrayUtilsCls mlArrayUtilsArrayUtils = new MlArrayUtilsArrayUtilsCls();

    public static ArrayList<ActivationFuncation> ACTIVATION_FUNCTIONS() {
        ArrayList<ActivationFuncation> arrays = new ArrayList<>();
        arrays.add(new ActivationFuncationTanh());
        arrays.add(new ActivationFuncationIdentity());
        arrays.add(new ActivationFuncationLogistic());
        arrays.add(new ActivationFuncationArctan());
        arrays.add(new ActivationFuncationSoftsign());
        arrays.add(new ActivationFuncationRelu());
        arrays.add(new ActivationFuncationSoftplus());
        arrays.add(new ActivationFuncationBent());
        arrays.add(new ActivationFuncationSinusoid());
        arrays.add(new ActivationFuncationSinc());
        arrays.add(new ActivationFuncationGaussian());
        arrays.add(new ActivationFuncationParametricRelu());
        arrays.add(new ActivationFuncationExponentialElu());
        arrays.add(new ActivationFuncationSoftExponential());
        arrays.add(new ActivationFuncationTanh());
        return arrays;
    }

    public static FeedforwardNeuralNetworksUtilsCls feedforwardNeuralNetworksUtils = new FeedforwardNeuralNetworksUtilsCls();


}

class NormalError extends Exception {
    private String desc;

    public NormalError(String desc) {
        super(desc);
        this.desc = desc;
    }

    public String getDescription() {
        return desc;
    }
}

class Tuple<A, B> {

    public final A first;

    public final B second;

    public Tuple(A a, B b) {
        first = a;
        second = b;
    }

    public String toString() {
        return "(" + first + ", " + second + ")";
    }

}

class ThreeTuple<A, B, C> extends Tuple<A, B> {

    public final C third;

    public ThreeTuple(A a, B b, C c) {
        super(a, b);
        third = c;
    }

    public String toString() {
        return "(" + first + "," + second + "," + third + ")";
    }

}

class RangeError extends RuntimeException {
    public RangeError(String message) {
        super(message);
    }
}


@FunctionalInterface
interface CompareFunction {
    void applay();
}
class MlOptions {
    public boolean inPlace = true;
    public int max = 0;
    public int min = 0;
    public int from = 0;
    public int to = 0;
    public int numberOfPoints = 0;
    public String variant = "";
    public boolean computeLeftSingularVectors = false;
    public boolean computeRightSingularVectors = false;
    public boolean autoTranspose = false;
    public int[] hiddenLayers = new int[0];
    public int iterations;
    public double learningRate;
    public NeuralNetworkDict dicts;
    public String model;
    public boolean assumeSymmetric = false;
    public int inputSize;
    public int outputSize;
    public Double regularization;
    public double epsilon;
    public String activation;
    public double activationParam;
    public List<MlOptions> layers = new ArrayList<>();
    public Matrix w;
    public Matrix b;
    public Map<String, Boolean> defaultOptions = new HashMap<>();

    MlOptions() {
        this.defaultOptions.put("assumeSymmetric", false);
    }
}

// ml-stat array.js
class MlStatArrayCls {

    public int compareNumbers(int a, int b) {
        return a - b;
    }

    public double sum(double[] values) {
        double sum = 0;
        for (int i = 0; i < values.length; i++) {
            sum += values[i];
        }
        return sum;
    }

    public double max(double[] values) {
        double max = values[0];
        int l = values.length;
        for (int i = 1; i < l; i++) {
            if (values[i] > max) {
                max = values[i];
            }
        }
        return max;
    }


    public double min(double[] values) {
        double min = values[0];
        int l = values.length;
        for (int i = 1; i < l; i++) {
            if (values[i] < min) {
                min = values[i];
            }
        }
        return min;
    }

    public Tuple<Double, Double> minMax(double[] values) {
        double min = values[0];
        double max = values[0];
        int l = values.length;
        for (int i = 1; i < l; i++) {
            if (values[i] < min) {
                min = values[i];
            }
            if (values[i] > max) {
                max = values[i];
            }
        }
        return new Tuple<>(min, max);
    }

    public double arithmeticMean(double[] values) {
        double sum = 0;
        int l = values.length;
        for (int i = 0; i < values.length; i++) {
            sum += values[i];
        }
        return sum / l;
    }


    public double mean(double[] values) {
        return arithmeticMean(values);
    }

    public double geometricMean(double[] values) {
        double mul = 1;
        int l = values.length;
        for (int i = 0; i < values.length; i++) {
            mul *= values[i];
        }
        return Math.pow(mul, (double) 1 / l);
    }

    public double logMean(double[] values) {
        double lnsum = 0;
        int l = values.length;
        for (int i = 0; i < values.length; i++) {
            lnsum += Math.log(values[i]);
        }
        return lnsum / l;
    }

    public double grandMean(double[] means, double[] samples) {
        double sum = 0;
        double n = 0;
        int l = means.length;
        for (int i = 0; i < l; i++) {
            sum += samples[i] * means[i];
            n += samples[i];
        }
        return sum / n;
    }

    public double truncatedMean(double[] values, double percent, Boolean alreadySorted) {
        Boolean newAlreadySorted = alreadySorted;
        double[] newValues = values;

        if (alreadySorted == null) {
            newAlreadySorted = false;
        }

        if (!alreadySorted) {
            Arrays.sort(values);
        }
        int l = values.length;
        double k = Math.floor(l * percent);
        double sum = 0;
        for (int i = (int) k; i < (l - (int) k); i++) {
            sum += newValues[i];
        }
        return sum / (l - MlConstrants.MLINDEX_BYTES_NUM_2 * k);
    }

    public double harmonicMean(double[] values) throws NormalError {
        double sum = 0;
        int l = values.length;
        for (int i = 0; i < l; i++) {
            if (values[i] == 0) {
                throw new RangeError("value at index " + i + "is zero");
            }
            sum += 1 / values[i];
        }
        return l / sum;
    }

    public double contraHarmonicMean(double[] values) {
        double r1 = 0;
        double r2 = 0;
        int l = values.length;
        for (int i = 0; i < values.length; i++) {
            r1 += values[i] * values[i];
            r2 += values[i];
        }
        if (r2 < 0) {
            throw new RangeError("sum of values is negative");
        }
        return r1 / r2;
    }

    public double median(double[] values, Boolean alreadySorted) {
        Boolean newAlreadySorted = alreadySorted;
        if (alreadySorted == null) {
            newAlreadySorted = false;
        }
        double[] newValues = values;
        if (!newAlreadySorted) {
            Arrays.sort(newValues);
        }
        int l = newValues.length;
        int half = (int) Math.floor((double) l / MlConstrants.MLINDEX_BYTES_NUM_2);
        if (l % MlConstrants.MLINDEX_BYTES_NUM_2 == 0) {
            return (newValues[half - 1] + newValues[half]) * MlConstrants.MLINDEX_BYTES_NUM_05;
        } else {
            return newValues[half];
        }
    }

    public double variance(double[] values, Boolean unbiased) {
        Boolean newUnbiased = unbiased;
        if (unbiased == null) {
            newUnbiased = true;
        }
        double theMean = new MlStatArrayCls().mean(values);
        double theVariance = 0;
        int l = values.length;

        for (int i = 0; i < values.length; i++) {
            double x = values[i] - theMean;
            theVariance += x * x;
        }

        if (newUnbiased) {
            return theVariance / (l - 1);
        } else {
            return theVariance / l;
        }
    }

    public double standardDeviation(double[] values, Boolean unbiased) {
        return Math.sqrt(this.variance(values, unbiased));
    }

    public double standardError(double[] values) {
        return this.standardDeviation(values, false) / Math.sqrt(values.length);
    }

    public Tuple robustMeanAndStdev(double[] y) {
        double mean = 0;
        double stdev = 0;
        int length = y.length;
        for (int i = 0; i < length; i++) {
            mean += y[i];
        }
        mean /= length;
        double[] averageDeviations = new double[length];
        for (int i = 0; i < length; i++) {
            averageDeviations[i] = Math.abs(y[i] - mean);
        }
        Arrays.sort(averageDeviations);
        if (length % MlConstrants.MLINDEX_BYTES_NUM_2 == 1) {
            stdev = averageDeviations[(length - 1) / MlConstrants.MLINDEX_BYTES_NUM_2] / MlConstrants.MLINDEX_BYTES_NUM_06745;
        } else {
            stdev =
                    MlConstrants.MLINDEX_BYTES_NUM_05 * (averageDeviations[length / MlConstrants.MLINDEX_BYTES_NUM_2] + averageDeviations[length / MlConstrants.MLINDEX_BYTES_NUM_2 - 1]) /
                            MlConstrants.MLINDEX_BYTES_NUM_06745;
        }

        return new Tuple(mean, stdev);
    }

    public ThreeTuple quartiles(double[] values, Boolean alreadySorted) {
        Boolean newAlreadySorted = alreadySorted;
        if (alreadySorted == null) {
            newAlreadySorted = false;
        }
        double[] newValues = values;
        if (!newAlreadySorted) {
            Arrays.sort(newValues);
        }

        int quart = newValues.length / MlConstrants.MLINDEX_BYTES_NUM_4;
        double q1 = newValues[(int) (ceil(quart) - 1)];
        double q2 = this.median(newValues, true);
        double q3 = newValues[(int) ceil(quart * MlConstrants.MLINDEX_BYTES_NUM_3) - 1];

        return new ThreeTuple<>(q1, q2, q3);
    }

    public double pooledStandardDeviation(double[][] samples, Boolean unbiased) {
        return Math.sqrt(this.pooledVariance(samples, unbiased));
    }

    public double pooledVariance(double[][] samples, Boolean unbiased) {
        Boolean newUnbiased = unbiased;
        if (unbiased == null) {
            newUnbiased = true;
        }
        double sum = 0;
        int count = 0;
        int l = samples.length;
        for (int i = 0; i < l; i++) {
            double[] values = samples[i];
            double vari = this.variance(values, false);
            sum += (values.length - 1) * vari;
            if (newUnbiased) {
                count += values.length - 1;
            } else {
                count += values.length;
            }
        }
        return sum / count;
    }

    public double mode(double[] values) {
        int l = values.length;
        double[] itemCount = new double[l];
        for (int i = 0; i < l; i++) {
            itemCount[i] = 0;
        }
        List<Double> itemArray = new ArrayList<>(l);

        int count = 0;

        for (int i = 0; i < values.length; i++) {
            int index = itemArray.indexOf(values[i]);
            if (index >= 0)
                itemCount[index]++;
            else {
                itemArray.set(count, values[i]);
                itemCount[count] = 1;
                count += 1;
            }
        }

        double maxValue = 0;
        int maxIndex = 0;
        for (int i = 0; i < count; i++) {
            if (itemCount[i] > maxValue) {
                maxValue = itemCount[i];
                maxIndex = i;
            }
        }
        return itemArray.get(maxIndex);
    }

    public double covariance(double[] vector1, double[] vector2, Boolean unbiased) {
        Boolean newUnbiased = unbiased;
        if (unbiased == null) {
            newUnbiased = true;
        }
        double mean1 = this.mean(vector1);
        double mean2 = this.mean(vector2);

        if (vector1.length != vector2.length) {
            throw new Error("Vectors do not have the same dimensions");
        }

        double cov = 0;
        int l = vector1.length;
        for (int i = 0; i < l; i++) {
            double x = vector1[i] - mean1;
            double y = vector2[i] - mean2;
            cov += x * y;
        }
        if (newUnbiased) {
            return cov / (l - 1);
        } else {
            return cov / l;
        }
    }

    public double skewness(double[] values, Boolean unbiased) {
        Boolean newUnbiased = unbiased;
        if (unbiased == null) {
            newUnbiased = true;
        }
        double theMean = this.mean(values);

        double s2 = 0;
        double s3 = 0;
        int l = values.length;
        for (int i = 0; i < values.length; i++) {
            double dev = values[i] - theMean;
            s2 += dev * dev;
            s3 += dev * dev * dev;
        }
        double m2 = s2 / l;
        double m3 = s3 / l;

        double g = m3 / (Math.pow(m2, MlConstrants.MLINDEX_BYTES_NUM_3 / MlConstrants.MLINDEX_BYTES_NUM_20));
        if (newUnbiased) {
            double a = Math.sqrt(l * (l - 1));
            int b = l - MlConstrants.MLINDEX_BYTES_NUM_2;
            return (a / b) * g;
        } else {
            return g;
        }
    }

    public double kurtosis(double[] values, Boolean unbiased) {
        Boolean newUnbiased = unbiased;
        if (unbiased == null) {
            newUnbiased = true;
        }
        double theMean = this.mean(values);
        int n = values.length;
        double s2 = 0;
        double s4 = 0;
        for (int i = 0; i < n; i++) {
            double dev = values[i] - theMean;
            s2 += dev * dev;
            s4 += dev * dev * dev * dev;
        }

        double m2 = s2 / n;
        double m4 = s4 / n;

        if (newUnbiased) {
            double v = s2 / (n - 1);
            int a = (n * (n + 1)) / ((n - 1) * (n - MlConstrants.MLINDEX_BYTES_NUM_2) * (n - MlConstrants.MLINDEX_BYTES_NUM_3));
            double b = s4 / (v * v);
            int c = ((n - 1) * (n - 1)) / ((n - MlConstrants.MLINDEX_BYTES_NUM_2) * (n - MlConstrants.MLINDEX_BYTES_NUM_3));

            return a * b - MlConstrants.MLINDEX_BYTES_NUM_3 * c;
        } else {
            return m4 / (m2 * m2) - MlConstrants.MLINDEX_BYTES_NUM_3;
        }
    }

    public double entropy(double[] values, Double eps) {
        Double newEps = eps;
        if (eps == null) {
            newEps = 0.0;
        }
        double sum = 0;
        double l = values.length;
        for (int i = 0; i < l; i++) {
            sum += values[i] * Math.log(values[i] + newEps);
        }
        return -sum;
    }

    public double weightedMean(double[] values, double[] weights) {
        double sum = 0;
        int l = values.length;
        for (int i = 0; i < l; i++) {
            sum += values[i] * weights[i];
        }
        return sum;
    }

    public double weightedStandardDeviation(double[] values, double[] weights) {
        return Math.sqrt(this.weightedVariance(values, weights));
    }

    public double weightedVariance(double[] values, double[] weights) {
        double theMean = this.weightedMean(values, weights);
        double vari = 0;
        int l = values.length;
        double a = 0;
        double b = 0;
        for (int i = 0; i < l; i++) {
            double z = values[i] - theMean;
            double w = weights[i];

            vari += w * (z * z);
            b += w;
            a += w * w;
        }
        return vari * (b / (b * b - a));
    }

    public void center(double[] values, Boolean inPlace) {
        Boolean newInPlace = inPlace;
        if (inPlace == null) {
            newInPlace = false;
        }

        double[] result = values;
        if (!newInPlace) {
            result = values;
        }

        double theMean = this.mean(result);
        int l = result.length;
        for (int i = 0; i < l; i++) {
            result[i] -= theMean;
        }
    }

    public double[] standardize(double[] values, Double standardDev, Boolean inPlace) {
        Double newStandardDev = standardDev;
        Boolean newInPlace = inPlace;
        if (standardDev == null) {
            newStandardDev = this.standardDeviation(values, false);
        }
        if (inPlace == null) {
            newInPlace = false;
        }
        int l = values.length;
        double[] result = newInPlace ? values : new double[l];
        for (int i = 0; i < l; i++) {
            result[i] = values[i] / newStandardDev;
        }
        return result;
    }

    public double[] cumulativeSum(double[] array) {
        int l = array.length;
        double[] result = new double[l];
        Arrays.fill(result, 0);
        result[0] = array[0];
        for (int i = 1; i < l; i++) {
            result[i] = result[i - 1] + array[i];
        }
        return result;
    }

}

class MlStatMatrix {

    public double compareNumbers(double a, double b) {
        return a - b;
    }

    public double max(double[][] matrix) {
        double max = Double.NEGATIVE_INFINITY;
        for (int i = 0; i < matrix.length; i++) {
            for (int j = 0; j < matrix[i].length; j++) {
                if (matrix[i][j] > max) {
                    max = matrix[i][j];
                }
            }
        }
        return max;
    }

    public double min(double[][] matrix) {
        double min = Double.POSITIVE_INFINITY;
        for (int i = 0; i < matrix.length; i++) {
            for (int j = 0; j < matrix[i].length; j++) {
                if (matrix[i][j] < min) {
                    min = matrix[i][j];
                }
            }
        }
        return min;
    }

    public Tuple<Double, Double> minMax(double[][] matrix) {
        double min = Double.POSITIVE_INFINITY;
        double max = Double.NEGATIVE_INFINITY;
        for (int i = 0; i < matrix.length; i++) {
            for (int j = 0; j < matrix[i].length; j++) {
                if (matrix[i][j] < min) {
                    min = matrix[i][j];
                }
                if (matrix[i][j] > max) {
                    max = matrix[i][j];
                }
            }
        }
        return new Tuple<>(min, max);
    }

    public double entropy(double[][] matrix, Integer eps) {
        Integer newEps = eps;
        if (eps == null) {
            newEps = 0;
        }

        double sum = 0;
        int l1 = matrix.length;
        int l2 = matrix[0].length;
        for (int i = 0; i < l1; i++) {
            for (int j = 0; j < l2; j++) {
                sum += matrix[i][j] * Math.log(matrix[i][j] + newEps);

            }
        }
        return -sum;
    }

    public double[] mean(double[][] matrix, Integer dimension) {
        Integer newDimension = dimension;
        if (dimension == null) {
            newDimension = 0;
        }

        int rows = matrix.length;
        int cols = matrix[0].length;
        double[] theMean = new double[0];
        double n;

        if (newDimension == -1) {
            theMean = new double[]{0};
            n = rows * cols;
            for (int i = 0; i < rows; i++) {
                for (int j = 0; j < cols; j++) {
                    theMean[0] += matrix[i][j];
                }
            }
            theMean[0] /= n;
        } else if (newDimension == 0) {
            theMean = new double[cols];
            Arrays.fill(theMean,0);
            n = rows;
            for (int j = 0; j < cols; j++) {
                theMean[j] = 0;
                for (int i = 0; i < rows; i++) {
                    theMean[j] += matrix[i][j];
                }
                theMean[j] /= n;
            }
        } else if (newDimension == 1) {
            theMean = new double[rows];
            Arrays.fill(theMean, 0);
            n = cols;
            for (int j = 0; j < rows; j++) {
                theMean[j] = 0;
                for (int i = 0; i < cols; i++) {
                    theMean[j] += matrix[j][i];
                }
                theMean[j] /= n;
            }
        } else {
            throw new Error("Invalid dimension");
        }
        return theMean;
    }

    public double[] sum(double[][] matrix, Integer dimension) {
        Integer newDimension = dimension;
        if (dimension == null) {
            newDimension = 0;
        }

        int rows = matrix.length;
        int cols = matrix[0].length;
        double[] theSum = new double[0];

        if (newDimension == -1) {
            theSum = new double[]{0};
            for (int i = 0; i < rows; i++) {
                for (int j = 0; j < cols; j++) {
                    theSum[0] += matrix[i][j];
                }
            }
        } else if (newDimension == 0) {
            theSum = new double[cols];
            for (int j = 0; j < cols; j++) {
                theSum[j] = 0;
                for (int i = 0; i < rows; i++) {
                    theSum[j] += matrix[i][j];
                }
            }
        } else if (newDimension == 1) {
            theSum = new double[rows];
            Arrays.fill(theSum,0);
            for (int j = 0; j < rows; j++) {
                theSum[j] = 0;
                for (int i = 0; i < cols; i++) {
                    theSum[j] += matrix[j][i];
                }
            }
        } else {
            throw new Error("Invalid dimension");
        }
        return theSum;
    }

    public double[] product(double[][] matrix, Integer dimension) {
        Integer newDimension = dimension;
        if (dimension == null) {
            newDimension = 0;
        }

        int rows = matrix.length;
        int cols = matrix[0].length;
        double[] theProduct;
        if (newDimension == -1) {
            theProduct = new double[]{1};
            for (int i = 0; i < rows; i++) {
                for (int j = 0; j < cols; j++) {
                    theProduct[0] *= matrix[i][j];
                }
            }
        } else if (newDimension == 0) {
            theProduct = new double[cols];
            Arrays.fill(theProduct,0);
            for (int j = 0; j < cols; j++) {
                theProduct[j] = 1;
                for (int i = 0; i < rows; i++) {
                    theProduct[j] *= matrix[i][j];
                }
            }
        } else if (newDimension == 1) {
            theProduct = new double[rows];
            Arrays.fill(theProduct,0);
            for (int j = 0; j < rows; j++) {
                theProduct[j] = 1;
                for (int i = 0; i < cols; i++) {
                    theProduct[j] *= matrix[j][i];
                }
            }
        } else {
            throw new Error("Invalid dimension");
        }
        return theProduct;
    }

    public double[] standardDeviation(double[][] matrix, double[] means, Boolean unbiased) {
        double[] vari = this.variance(matrix, means, unbiased);
        int l = vari.length;
        for (int i = 0; i < l; i++) {
            vari[i] = Math.sqrt(vari[i]);
        }
        return vari;
    }

    public double[] variance(double[][] matrix, double[] means, Boolean unbiased) {
        Boolean newUnbiased = unbiased;
        if (unbiased == null) {
            newUnbiased = true;
        }
        double[] newMeans = means;
        if (means.length == 0) {
            newMeans = this.mean(matrix, null);
        }
        int rows = matrix.length;
        if (rows == 0) {
            return new double[0];
        }
        int cols = matrix[0].length;
        double[] vari = new double[cols];
        Arrays.fill(vari, 0.0);
        for (int j = 0; j < cols; j++) {
            double sum1 = 0;
            double sum2 = 0;
            double x = 0;
            for (int i = 0; i < rows; i++) {
                x = matrix[i][j] - newMeans[j];
                sum1 += x;
                sum2 += x * x;
            }
            if (newUnbiased) {
                vari[j] = (sum2 - (sum1 * sum1) / rows) / (rows - 1);
            } else {
                vari[j] = (sum2 - (sum1 * sum1) / rows) / rows;
            }
        }
        return vari;
    }

    public double[] median(double[][] matrix) {
        int rows = matrix.length;
        int cols = matrix[0].length;
        double[] medians = new double[cols];
        Arrays.fill(medians, 0);
        for (int i = 0; i < cols; i++) {
            double[] data = new double[rows];
            Arrays.fill(data, 0);
            for (int j = 0; j < rows; j++) {
                data[j] = matrix[j][i];
            }
            Arrays.sort(data);
            int n = data.length;
            if (n % MlConstrants.MLINDEX_BYTES_NUM_2 == 0) {
                medians[i] = (data[n / MlConstrants.MLINDEX_BYTES_NUM_2] + data[(n / MlConstrants.MLINDEX_BYTES_NUM_2) - 1]) * MlConstrants.MLINDEX_BYTES_NUM_05;
            } else {
                medians[i] = data[(int) Math.floor(n / MlConstrants.MLINDEX_BYTES_NUM_2)];
            }
        }
        return medians;
    }

    public double[] mode(double[][] matrix) {
        int rows = matrix.length;
        int cols = matrix[0].length;
        double[] modes = new double[cols];
        Arrays.fill(modes,0);
        for (int i = 0; i < cols; i++) {
            double[] itemCount = new double[rows];
            Arrays.fill(itemCount,0);
            for (int k = 0; k < rows; k++) {
                itemCount[k] = 0;
            }

            List<Double> itemArray = new ArrayList<>(rows);
            int count = 0;

            for (int j = 0; j < rows; j++) {
                int index = itemArray.indexOf(matrix[j][i]);
                if (index >= 0) {
                    itemCount[index] += 1;
                } else {
                    itemArray.set(count, matrix[j][i]);
                    itemCount[count] = 1;
                    count += 1;
                }
            }

            double maxValue = 0;
            double maxIndex = 0;
            for (int j = 0; j < count; j++) {
                if (itemCount[j] > maxValue) {
                    maxValue = itemCount[j];
                    maxIndex = j;
                }
            }
            modes[i] = itemArray.get((int) maxIndex);
        }
        return modes;
    }

    public double[] skewness(double[][] matrix, Boolean unbiased) {
        Boolean newUnbiased = unbiased;
        if (unbiased == null) {
            newUnbiased = true;
        }
        double[] means = this.mean(matrix, 0);
        int n = matrix.length;
        int l = means.length;
        double[] skew = new double[l];
        Arrays.fill(skew, 0.0);

        for (int j = 0; j < l; j++) {
            double s2 = 0;
            double s3 = 0;
            for (int i = 0; i < n; i++) {
                double dev = matrix[i][j] - means[j];
                s2 += dev * dev;
                s3 += dev * dev * dev;
            }
            double m2 = s2 / n;
            double m3 = s3 / n;
            double g = m3 / Math.pow(m2, MlConstrants.MLINDEX_BYTES_NUM_3 / MlConstrants.MLINDEX_BYTES_NUM_2);

            if (newUnbiased) {
                double a = Math.sqrt(n * (n - 1));
                int b = n - MlConstrants.MLINDEX_BYTES_NUM_2;
                skew[j] = (a / b) * g;
            } else {
                skew[j] = g;
            }
        }
        return skew;
    }

    public double[] kurtosis(double[][] matrix, Boolean unbiased) {
        Boolean newUnbiased = unbiased;
        if (unbiased == null) {
            newUnbiased = true;
        }

        double[] means = this.mean(matrix, 0);
        int n = matrix.length;
        int m = matrix[0].length;
        double[] kurt = new double[m];
        Arrays.fill(kurt, 0);
        for (int j = 0; j < m; j++) {
            double s2 = 0;
            double s4 = 0;

            for (double[] doubles : matrix) {
                double dev = doubles[j] - means[j];
                s2 += dev * dev;
                s4 += dev * dev * dev * dev;
            }
            double m2 = s2 / n;
            double m4 = s4 / n;

            if (newUnbiased) {
                double v = s2 / (n - 1);
                double a = (double) (n * (n + 1)) / ((n - 1) * (n - MlConstrants.MLINDEX_BYTES_NUM_2) * (n - MlConstrants.MLINDEX_BYTES_NUM_3));
                double b = s4 / (v * v);
                double c = (double) ((n - 1) * (n - 1)) / ((n - MlConstrants.MLINDEX_BYTES_NUM_2) * (n - MlConstrants.MLINDEX_BYTES_NUM_3));
                kurt[j] = a * b - MlConstrants.MLINDEX_BYTES_NUM_3 * c;
            } else {
                kurt[j] = m4 / (m2 * m2) - MlConstrants.MLINDEX_BYTES_NUM_3;
            }
        }
        return kurt;
    }

    public double[] standardError(double[][] matrix) {
        int samples = matrix.length;
        double[] standardDeviations = this.standardDeviation(matrix, null, null);
        int l = standardDeviations.length;
        double[] standardErrors = new double[l];
        double sqrtN = Math.sqrt(samples);

        for (int i = 0; i < l; i++) {
            standardErrors[i] = standardDeviations[i] / sqrtN;
        }
        return standardErrors;
    }

    public double[][] covariance(double[][] matrix, Integer dimension) {
        return this.scatter(matrix, null, dimension);
    }

    public double[][] scatter(double[][] matrix, Integer divisor, Integer dimension) {
        Integer newDimension = dimension;
        Integer newDivisor = divisor;
        if (dimension == null) {
            newDimension = 0;
        }

        if (divisor == null) {
            if (newDimension == 0) {
                newDivisor = matrix.length - 1;
            } else if (dimension == 1) {
                newDivisor = matrix[0].length - 1;
            }
        }

        double[] means = this.mean(matrix, dimension);
        int rows = matrix.length;
        if (rows == 0) {
            return new double[0][0];
        }
        int cols = matrix[0].length;
        double[][] cov = new double[cols][];
        double s;
        if (newDimension == 0) {
            double[] arr = new double[cols];
            Arrays.fill(arr,0);
            for (int i = 0; i < cols; i++) {
                cov[i] = arr;
            }
            for (int i = 0; i < cols; i++) {
                for (int j = i; j < cols; j++) {
                    s = 0;
                    for (int k = 0; k < rows; k++) {
                        s += (matrix[k][j] - means[j]) * (matrix[k][i] - means[i]);
                    }
                    s /= newDivisor;
                    cov[i][j] = s;
                    cov[j][i] = s;
                }
            }
        } else if (newDimension == 1) {
            cov = new double[rows][];
            for (int i = 0; i < rows; i++) {
                double[] arr = new double[rows];
                Arrays.fill(arr,0);
                cov[i] = arr;
            }
            for (int i = 0; i < rows; i++) {
                for (int j = i; j < rows; j++) {
                    s = 0;
                    for (int k = 0; k < cols; k++) {
                        s += (matrix[j][k] - means[j]) * (matrix[i][k] - means[i]);
                    }
                    s /= newDivisor;
                    cov[i][j] = s;
                    cov[j][i] = s;
                }
            }
        } else {
            throw new Error("Invalid dimension");
        }

        return cov;
    }

    public double[][] correlation(double[][] matrix) {
        double[] means = this.mean(matrix, null);
        double[] standardDeviations = this.standardDeviation(matrix, means, true);
        double[][] scores = this.zScores(matrix, means, standardDeviations);
        int rows = matrix.length;
        int cols = matrix[0].length;
        double[][] cor = new double[cols][];

        for (int i = 0; i < cols; i++) {
            double[] arr = new double[cols];
            Arrays.fill(arr,0);
            cor[i] = arr;
        }

        for (int i = 0; i < cols; i++) {
            for (int j = i; j < cols; j++) {
                double c = 0;
                for (int k = 0; k < scores.length; k++) {
                    c += scores[k][j] * scores[k][i];
                }

                c /= rows - 1;
                cor[i][j] = c;
                cor[j][i] = c;
            }
        }
        return cor;
    }

    public double[][] zScores(double[][] matrix, double[] means, double[] standardDeviations) {
        double[] newStandardDeviations = standardDeviations;
        double[] newMeans = means;
        if (means.length == 0) {
            newMeans = this.mean(matrix, null);
        }
        if (standardDeviations.length == 0) {
            newStandardDeviations = this.standardDeviation(matrix, newMeans, true);
        }

        return this.standardize(this.center(matrix, newMeans, false), newStandardDeviations, true);
    }

    public double[][] center(double[][] matrix, double[] means, boolean inPlace) {
        double[] newMeans = means;
        if (means == null) {
            newMeans = this.mean(matrix, null);
        }
        double[][] result = matrix;
        int l = matrix.length;
        if (!inPlace) {

            result = new  double[l][];

            for (int i = 0; i < l; i++) {
                double[] arr = new double[matrix[i].length];
                Arrays.fill(arr,0);
                result[i] = arr;
            }
        }

        for (int i = 0; i < l; i++) {
            double[] row = result[i];
            for (int j = 0; j < row.length; j++) {
                row[j] = matrix[i][j] - newMeans[j];
            }
        }
        return result;
    }

    public double[][] standardize(double[][] matrix, double[] standardDeviations, boolean inPlace) {
        double[] newStandardDeviations = standardDeviations;
        if (standardDeviations.length == 0) {
            newStandardDeviations = this.standardDeviation(matrix, null, null);
        }

        double[][] result = matrix;
        int l = matrix.length;

        if (!inPlace) {
            result = new  double[l][];
            for (int i = 0; i < l; i++) {
                double[] arr = new double[matrix[i].length];
                Arrays.fill(arr,0);
                result[i] = arr;
            }
        }

        for (int i = 0; i < l; i++) {
            double[] resultRow = result[i];
            double[] sourceRow = matrix[i];
            for (int j = 0; j < resultRow.length; j++) {
                if (standardDeviations[j] != 0) {
                    resultRow[j] = sourceRow[j] / newStandardDeviations[j];
                }
            }
        }
        return result;
    }

    ;

    public double[] weightedVariance(double[][] matrix, double[] weights) {
        double[] means = this.mean(matrix, null);
        int rows = matrix.length;
        if (rows == 0) {
            return new double[0];
        }
        int cols = matrix[0].length;
        double[] vari = new double[cols];
        Arrays.fill(vari,0);

        for (int j = 0; j < cols; j++) {
            double sum = 0;
            double a = 0;
            double b = 0;

            for (int i = 0; i < rows; i++) {
                double z = matrix[i][j] - means[j];
                double w = weights[i];

                sum += w * (z * z);
                b += w;
                a += w * w;
            }

            vari[j] = sum * (b / (b * b - a));
        }
        return vari;
    }

    public double[] weightedMean(double[][] matrix, double[] weights, Integer dimension) {
        Integer newDimension = dimension;
        if (dimension == null) {
            newDimension = 0;
        }
        int rows = matrix.length;
        if (rows == 0) {
            return new double[0];
        }
        int cols = matrix[0].length;

        double[] means = new double[0];
        double w;
        double[] row;

        if (dimension == 0) {
            means = new double[cols];
            Arrays.fill(means,0);
            for (int i = 0; i < cols; i++) {
                means[i] = 0;
            }
            for (int i = 0; i < rows; i++) {
                row = matrix[i];
                w = weights[i];
                for (int j = 0; j < cols; j++) {
                    means[j] += row[j] * w;
                }
            }
        } else if (dimension == 1) {
            means = new double[rows];
            Arrays.fill(means,0);
            for (int i = 0; i < rows; i++) {
                means[i] = 0;
            }
            for (int j = 0; j < rows; j++) {
                row = matrix[j];
                w = weights[j];
                for (int i = 0; i < cols; i++) {
                    means[j] += row[i] * w;
                }
            }
        } else {
            throw new Error("Invalid dimension");
        }

        double weightSum = new MlStatArrayCls().sum(weights);
        if (weightSum != 0) {
            for (int i = 0; i < means.length; i++) {
                means[i] /= weightSum;
            }
        }
        return means;
    }

    public double[][] weightedCovariance(double[][] matrix, double[] weights, double[] means, Integer dimension) {
        double[] newMeans = means;
        Integer newDimension = dimension;
        if (dimension == null) {
            newDimension = 0;
        }

        if (means.length == 0) {
            newMeans = this.weightedMean(matrix, weights, dimension);
        }
        double s1 = 0;
        double s2 = 0;
        for (int i = 0; i < weights.length; i++) {
            s1 += weights[i];
            s2 += weights[i] * weights[i];
        }
        double factor = s1 / (s1 * s1 - s2);
        return this.weightedScatter(matrix, weights, newMeans, factor, newDimension);
    }

    public double[][] weightedScatter(double[][] matrix, double[] weights, double[] means, Double factor, Integer dimension) {
        Integer newDimension = dimension;
        double[] newMeans = means;
        Double newFactor = factor;
        if (newDimension == null) {
            newDimension = 0;
        }
        if (newMeans.length == 0) {
            newMeans = this.weightedMean(matrix, weights, newDimension);
        }
        if (factor == null) {
            newFactor = 1.0;
        }

        int rows = matrix.length;
        if (rows == 0) {
            return new double[0][0];
        }
        int cols = matrix[0].length;
        double[][] cov = new double[cols][];
        double s;

        if (newDimension == 0) {
            for (int i = 0; i < cols; i++) {
                double[] arr = new double[cols];
                Arrays.fill(arr, 0);
                cov[i] = arr;
            }
            for (int i = 0; i < cols; i++) {
                for (int j = i; j < cols; j++) {
                    s = 0;
                    for (int k = 0; k < rows; k++) {
                        s += weights[k] * (matrix[k][j] - means[j]) * (matrix[k][i] - means[i]);
                    }
                    cov[i][j] = s * factor;
                    cov[j][i] = s * factor;
                }
            }
        } else if (newDimension == 1) {
            cov = new double[rows][];
            for (int i = 0; i < rows; i++) {
                double[] arr = new double[rows];
                Arrays.fill(arr, 0);
                cov[i] = arr;
            }

            for (int i = 0; i < rows; i++) {
                for (int j = i; j < rows; j++) {
                    s = 0;
                    for (int k = 0; k < cols; k++) {
                        s += weights[k] * (matrix[j][k] - means[j]) * (matrix[i][k] - means[i]);
                    }
                    cov[i][j] = s * newFactor;
                    cov[j][i] = s * newFactor;
                }
            }
        } else {
            throw new Error("Invalid dimension");
        }
        return cov;
    }
}


// ml-array-utils ArrayUtils.js
class MlArrayUtilsCls {
    public static Double[] getEquallySpacedData(Integer[] x, Integer[] y, MlOptions options) {
        return MlArrayUtilsGetEquallySpaced.getEquallySpacedData(x, y, options);
    }

    public static double[] svn(double[] data) {
        double mean = new MlStatArrayCls().mean(data);
        double std = new MlStatArrayCls().standardDeviation(data, null);
        double[] result = new double[data.length - 1];
        System.arraycopy(data, 0, result, 0, data.length - 1);
        for (int i = 0; i < data.length; i++) {
            result[i] = (result[i] - mean) / std;
        }
        return result;
    }
}

// ml-array-utils getEquallySpaced.js
class MlArrayUtilsGetEquallySpaced {
    public static Double[] getEquallySpacedData(Integer[] x, Integer[] y, MlOptions options) {
        MlOptions newOptions = options;
        Integer[] newX = x;
        Integer[] newY = y;
        if (x.length > 1 && x[0] > x[1]) {
            newX = IntStream.range(0, x.length).map(i -> x.length - 1 - i).mapToObj(i -> x[i]).toArray(Integer[]::new); ;
            newY = IntStream.range(0, y.length).map(i -> y.length - 1 - i).mapToObj(i -> y[i]).toArray(Integer[]::new); ;
        }
        int xLength = x.length;
        if (xLength != y.length) {
            throw new RangeError("the x and y vector does't have the same size.");
        }
        int from;
        if (options == null) {
            newOptions = new MlOptions();
        }
        if (newOptions.from == 0) {
            from = x[0];
        } else {
            from = newOptions.from;
        }

        int to = newOptions.to == 0 ? newX[newX.length - 1] : newOptions.to;

        boolean reverse = from > to;
        if (reverse) {
            int temp = from;
            from = to;
            to = temp;
        }
        int numberOfPoints = newOptions.numberOfPoints == 0 ? MlConstrants.MLINDEX_BYTES_NUM_100 : newOptions.numberOfPoints;
        String algorithm = newOptions.variant.equals("slot") ? "slot" : "smooth"; // default value: smooth
        Double[] output = algorithm.equals("slot") ?
                getEquallySpacedSlot(newX, newY, from, to, numberOfPoints) :
                getEquallySpacedSmooth(newX, newY, from, to, numberOfPoints);
        if (reverse) {
           return  IntStream.range(0, output.length).map(i -> output.length - 1 - i).mapToObj(i -> output[i]).toArray(Double[]::new);
        } else {
            return output;
        }
    }
    public static int getSlope(int x0, int y0, int x1, int y1) {
        return (y1 - y0) / (x1 - x0);
    }

    public static Double[] getEquallySpacedSmooth(Integer[] x, Integer[] y, double from, double to, int numberOfPoints) {
        int xLength = x.length;

        double step = (to - from) / (numberOfPoints - 1);
        double halfStep = step / MlConstrants.MLINDEX_BYTES_NUM_2;

        double start = from - halfStep;
        Double[] output = new Double[numberOfPoints];

        double initialOriginalStep = x[1] - x[0];
        double lastOriginalStep = x[x.length - 1] - x[x.length - MlConstrants.MLINDEX_BYTES_NUM_2];

        double min = start;
        double max = start + step;

        double previousX = Double.MIN_VALUE;
        int previousY = 0;
        double nextX = x[0] - initialOriginalStep;
        int nextY = 0;

        double currentValue = 0;
        double slope = 0;
        double intercept = 0;
        double sumAtMin = 0;
        double sumAtMax = 0;

        int i = 0;
        int j = 0;
        boolean mainWhile = true;
        main:
        while (mainWhile) {
            while (nextX - max >= 0) {
                // no overlap with original point, just consume current value
                int add = integral(0, max - previousX, slope, previousY);
                sumAtMax = currentValue + add;

                output[j] = (sumAtMax - sumAtMin) / step;
                j += 1;

                if (j == numberOfPoints) {
                    break main;
                }

                min = max;
                max += step;
                sumAtMin = sumAtMax;
            }

            if (previousX <= min && min <= nextX) {
                int add = integral(0, min - previousX, slope, previousY);
                sumAtMin = currentValue + add;
            }

            currentValue += integral(previousX, nextX, slope, intercept);

            previousX = nextX;
            previousY = nextY;

            if (i < xLength) {
                nextX = x[i];
                nextY = (int) y[i];
                i += 1;
            } else if (i == xLength) {
                nextX += lastOriginalStep;
                nextY = 0;
            }
            // updating parameters
            slope = getSlope((int) previousX, previousY, (int) nextX, nextY);
            intercept = -slope * previousX + previousY;
        }

        return output;
    }


    public static Double[] getEquallySpacedSlot(Integer[] x, Integer[] y, double from, double to, int numberOfPoints) {
        int xLength = x.length;

        double step = (to - from) / (numberOfPoints - 1);
        double halfStep = step / MlConstrants.MLINDEX_BYTES_NUM_2;
        double lastStep = x[x.length - 1] - x[x.length - MlConstrants.MLINDEX_BYTES_NUM_2];

        double start = from - halfStep;
        Double[] output = new Double[numberOfPoints];
        Arrays.fill(output, 0);

        // Init main variables
        double min = start;
        double max = start + step;

        double previousX = -Double.MAX_VALUE;
        double previousY = 0;
        double nextX = x[0];
        double nextY = y[0];
        int frontOutsideSpectra = 0;
        boolean backOutsideSpectra = true;

        int currentValue = 0;

        // for slot algorithm
        int currentPoints = 0;

        int i = 1; // index of input
        int j = 0; // index of output

        boolean mainWhile = true;
        main:
        while (mainWhile) {
            if (previousX >= nextX) {
                throw new Error("x must be an increasing serie");
            }
            while (previousX - max > 0) {
                if (backOutsideSpectra) {
                    currentPoints += 1;
                    backOutsideSpectra = false;
                }

                output[j] = (double) (currentPoints <= 0 ? 0 : currentValue / currentPoints);
                j += 1;

                if (j == numberOfPoints) {
                    break main;
                }
                min = max;
                max += step;
                currentValue = 0;
                currentPoints = 0;
            }

            if (previousX > min) {
                currentValue += previousY;
                currentPoints += 1;
            }

            if (previousX == -Double.MAX_VALUE || frontOutsideSpectra > 1)
                currentPoints -= 1;

            previousX = nextX;
            previousY = nextY;

            if (i < xLength) {
                nextX = x[i];
                nextY = y[i];
                i += 1;
            } else {
                nextX += lastStep;
                nextY = 0;
                frontOutsideSpectra += 1;
            }
        }

        return output;
    }

    public static int integral(double x0, double x1, double slope, double intercept) {
        return (int) ((MlConstrants.MLINDEX_BYTES_NUM_05 * slope * x1 * x1 + intercept * x1) - (MlConstrants.MLINDEX_BYTES_NUM_05 * slope * x0 * x0 + intercept * x0));
    }


}

// ml-array-utils snv.js
class MlArrayUtilsSnvCls {
    public static double[] svn(double[] data) {
        double mean = new MlStatArrayCls().mean(data);
        double std = new MlStatArrayCls().standardDeviation(data, null);
        double[] result = new double[data.length - 1];
        System.arraycopy(data, 0, result, 0, data.length - 1);
        for (int i = 0; i < data.length; i++) {
            result[i] = (result[i] - mean) / std;
        }
        return result;
    }
}

class MlMatrixUtilCls {
    public void checkRowIndex(Matrix matrix, int index, boolean outer) {
        int max = outer ? matrix.rows : matrix.rows - 1;
        if (index < 0 || index > max) {
            throw new RangeError("Row index out of range");
        }
    }

    public void checkColumnIndex(Matrix matrix, int index, boolean outer) {
        int max = outer ? matrix.columns : matrix.columns - 1;
        if (index < 0 || index > max) {
            throw new RangeError("Column index out of range");
        }
    }

    public double[] checkRowVector(Matrix matrix, Object vector) {
        if (vector instanceof Matrix) {
            return ((Matrix) vector).to1dArray();
        }
        if (vector instanceof double[]) {
            if (((double[]) vector).length != matrix.columns) {
                throw new Error("vector size must be the same as the number of columns");
            }
        }
        return (double[]) vector;
    }

    public double[] checkColumnVector(Matrix matrix, Object vector) {
        if (vector instanceof Matrix) {
            return ((Matrix) vector).to1dArray();
        }
        if (vector instanceof double[]) {
            if (((double[]) vector).length != matrix.rows) {
                throw new RangeError("vector size must be the same as the number of rows");
            }
        }
        return (double[]) vector;
    }


    public Tuple checkIndices(Matrix matrix, int[] rowIndices, int[] columnIndices) {

        boolean rowOut = IntStream.range(0, rowIndices.length).anyMatch(r -> r < 0 || r >= matrix.rows);

        boolean columnOut = IntStream.range(0, columnIndices.length).anyMatch(r -> r < 0 || r >= matrix.columns);

        if (rowOut || columnOut) {
            throw new RangeError("Indices are out of range");
        }
        return new Tuple(rowIndices, columnIndices);
    }


    public void checkRange(Matrix matrix, Integer startRow, Integer endRow, Integer startColumn, Integer endColumn) {
        if (matrix == null || startRow == null || endRow == null || startColumn == null || endColumn == null) {
            throw new RangeError("Invalid argument type");
        }
    }

    public double[] getRange(int from, int to) {
        double[] arr = new double[to - from + 1];
        Arrays.fill(arr, 0);
        for (int i = 0; i < arr.length; i++) {
            arr[i] = from + i;
        }
        return arr;
    }

    public Matrix sumByRow(Matrix matrix) {
        Matrix sum = Matrix.zeros(matrix.rows, 1);
        for (int i = 0; i < matrix.rows; ++i) {
            for (int j = 0; j < matrix.columns; ++j) {
                sum.set(i, 0, sum.get(i, 0) + matrix.get(i, j));
            }
        }
        return sum;
    }

    public Matrix sumByColumn(Matrix matrix) {
        Matrix sum = Matrix.zeros(1, matrix.columns);
        for (int i = 0; i < matrix.rows; ++i) {
            for (int j = 0; j < matrix.columns; ++j) {
                sum.set(0, j, sum.get(0, j) + matrix.get(i, j));
            }
        }
        return sum;
    }

    public double sumAll(Matrix matrix) {
        double v = 0;
        for (int i = 0; i < matrix.rows; i++) {
            for (int j = 0; j < matrix.columns; j++) {
                v += matrix.get(i, j);
            }
        }
        return v;
    }
}

// ml-matrix src/dc/util.js
class MlMatrixDcuTil {

    public int hypotenuse(double a, double b) {
        double r;
        if (Math.abs(a) > Math.abs(b)) {
            r = (double) b / a;
            return (int) (Math.abs(a) * Math.sqrt(1 + r * r));
        }
        if (b != 0) {
            r = (double) a / b;
            return (int) (Math.abs(b) * Math.sqrt(1 + r * r));
        }
        return 0;
    }
    // For use in the decomposition algorithms. With big matrices, access time is
    // too long on elements from array subclass
    // todo check when it is fixed in v8
    // http://jsperf.com/access-and-write-array-subclass

    public double[][] getEmpty2DArray(int rows, int columns) {
        double[][] array = new double[rows][];
        for (int i = 0; i < rows; i++) {
            double[] arr = new double[columns];
            Arrays.fill(array, 0.0);
            array[i] = arr;
        }
        return array;
    }

    public double[][] getFilled2dArray(int rows, int columns, double value) {
        double[][] array = new double[rows][];
        for (int i = 0; i < rows; i++) {
            double[] arr = new double[columns];
            Arrays.fill(arr, 0.0);
            array[i] = arr;
            for (int j = 0; j < columns; j++) {
                array[i][j] = value;
            }
        }
        return array;
    }

}

// ml-matrix src/dc/lu.js
class MlMatrixDclu {

    public Matrix lu;
    public Matrix matrix;
    public int[] pivotVector;
    public double pivotSign;

    MlMatrixDclu(Matrix matrix) {
        Matrix newMatrix = matrix;
        newMatrix = Matrix.checkMatrix(matrix);
        this.matrix = newMatrix;

        Matrix lu = newMatrix.clone();
        int rows = lu.rows;
        int columns = lu.columns;
        int[] pivotVector = new int[0];
        double pivotSign = 1.0;
        int p;
        double s;
        double t;
        int v;
        double[] luRowi;
        double[] luColj;
        int kmax;
        for (int i = 0; i < rows; i++) {
            pivotVector[i] = i;
        }

        luColj = new double[rows];
        Arrays.fill(luColj, 0.0);

        for (int j = 0; j < columns; j++) {
            for (int i = 0; i < rows; i++) {
                luColj[i] = lu.get(i, j);
            }
            for (int i = 0; i < rows; i++) {
                luRowi = lu.getRow(i);
                kmax = Math.min(i, j);
                s = 0;
                for (int k = 0; k < kmax; k++) {
                    s += luRowi[k] * luColj[k];
                }

                luColj[i] = luColj[i] - s;
                luRowi[j] = luColj[i];
            }


            p = j;
            for (int i = j + 1; i < rows; i++) {
                if (Math.abs(luColj[i]) > Math.abs(luColj[p])) {
                    p = i;
                }

            }

            if (p != j) {
                for (int k = 0; k < columns; k++) {
                    t = lu.get(p, k);
                    lu.set(p, k, lu.get(j, k));
                    lu.set(j, k, t);
                }
                v = pivotVector[p];
                pivotVector[p] = pivotVector[j];
                pivotVector[j] = v;

                pivotSign = -pivotSign;
            }

            if (j < rows && lu.get(j, j) != 0) {
                for (int i = j + 1; i < rows; i++) {
                    lu.set(i, j, lu.get(i, j) / lu.get(j, j));
                }
            }
        }
        this.lu = lu;
        this.pivotVector = pivotVector;
        this.pivotSign = pivotSign;
    }

    public boolean isSingular() {
        Matrix data = this.lu;
        int col = data.columns;

        for (int j = 0; j < col; j++) {
            if (data.get(j, j) == 0) {
                return true;
            }
        }

        return false;
    }

    public double determinant() {
        Matrix data = this.lu;
        if (!data.isSquare()) {
            throw new Error("Matrix must be square");
        }
        double determinant = this.pivotSign;
        int col = data.columns;
        for (int j = 0; j < col; j++) {
            determinant *= data.get(j, j);
        }
        return determinant;
    }

    public Matrix lowerTriangularMatrix() {
        Matrix data = this.lu;
        int rows = data.rows;
        int columns = data.columns;
        Matrix x = new Matrix(rows, columns);

        for (int i = 0; i < rows; i++) {
            for (int j = 0; j < columns; j++) {
                if (i > j) {
                    x.set(i, j, data.get(i, j));
                } else if (i == j) {
                    x.set(i, j, 1);
                } else {
                    x.set(i, j, 0);
                }
            }
        }
        return x;
    }

    public Matrix upperTriangularMatrix() {
        Matrix data = this.lu;
        int rows = data.rows;
        int columns = data.columns;
        Matrix x = new Matrix(rows, columns);

        for (int i = 0; i < rows; i++) {
            for (int j = 0; j < columns; j++) {
                if (i <= j) {
                    x.set(i, j, data.get(i, j));
                } else {
                    x.set(i, j, 0);
                }
            }
        }
        return x;
    }

    public int[] pivotPermutationVector() {
        return Arrays.copyOf(this.pivotVector, this.pivotVector.length);
    }

    public Matrix solve(Matrix value) {
        value = Matrix.checkMatrix(value);

        Matrix lu = this.lu;
        int rows = lu.rows;

        if (rows != value.rows) {
            throw new Error("Invalid matrix dimensions");
        }
        if (this.isSingular()) {
            throw new Error("LU matrix is singular");
        }

        int count = value.columns;
        Matrix x = value.subMatrixRow(this.pivotVector, 0, count - 1);
        int columns = lu.columns;

        for (int k = 0; k < columns; k++) {
            for (int i = k + 1; i < columns; i++) {
                for (int j = 0; j < count; j++) {
                    x.set(i, j, x.get(i, j) - x.get(k, j) * lu.get(i, k));
                }
            }
        }
        for (int k = columns - 1; k >= 0; k--) {
            for (int j = 0; j < count; j++) {
                x.set(k, j, x.get(k, j) / lu.get(k, k));
            }
            for (int i = 0; i < k; i++) {
                for (int j = 0; j < count; j++) {
                    x.set(i, j, x.get(i, j) - x.get(k, j) * lu.get(i, k));

                }
            }
        }
        return x;
    }
}

// ml-matrix src/dc/svd.js
class MlMatrixDcsvd {
    public int m = 0;
    public int n = 0;
    public double[] s = new double[0];
    public Object u;
    public Object v;

    MlMatrixDcsvd(Matrix value, MlOptions options) {
        value = Matrix.checkMatrix(value);
        int m = value.rows;
        int n = value.columns;
        int nu = Math.min(m, n);

        boolean wantu = true;
        boolean wantv = true;
        if (!options.computeLeftSingularVectors) {
            wantu = false;
        }
        if (!options.computeRightSingularVectors) {
            wantv = false;
        }
        boolean autoTranspose = options.autoTranspose;

        boolean swapped = false;
        Matrix a;
        if (m < n) {
            if (!autoTranspose) {
                a = value.clone();
                // eslint-disable-next-line no-console
                System.out.println("Computing SVD on a matrix with more columns than rows. Consider enabling autoTranspose");
            } else {
                a = value.transpose();
                m = a.rows;
                n = a.columns;
                swapped = true;
                boolean aux = wantu;
                wantu = wantv;
                wantv = aux;
            }
        } else {
            a = value.clone();
        }
        double[] s = new double[Math.min(m + 1, n)];
        Arrays.fill(s, 0.0);
        double[][] u = MlConstrants.mlMatrixDcuTil.getFilled2dArray(m, nu, 0);
        double[][] v = MlConstrants.mlMatrixDcuTil.getFilled2dArray(n, n, 0);
        double[] e = new double[n];
        Arrays.fill(e, 0.0);
        double[] work = new double[m];
        Arrays.fill(work, 0.0);


        int nct = Math.min(m - 1, n);
        int nrt = Math.max(0, Math.min(n - MlConstrants.MLINDEX_BYTES_NUM_2, m));

        int p;
        double t;
        double f;
        double cs;
        double sn;
        int kase;
        double scale;
        double sp;
        double spm1;
        double epm1;
        double sk;
        double ek;
        double b;
        double c;
        double shift;
        double g;

        for (int k = 0; k < Math.max(nct, nrt); k++) {
            if (k < nct) {
                s[k] = 0;
                for (int i = k; i < m; i++) {

                    s[k] = MlConstrants.mlMatrixDcuTil.hypotenuse(s[k], a.get(i, k));
                }
                if (s[k] != 0) {
                    if (a.get(k, k) < 0) {
                        s[k] = -s[k];
                    }
                    for (int i = k; i < m; i++) {

                        a.set(i, k, a.get(i, k) / s[k]);
                    }
                    a.set(k, k, a.get(k, k) + 1);
                }
                s[k] = -s[k];
            }
            for (int j = k; j < n; j++) {

                if (k < nct && s[k] != 0) {
                    t = 0;
                    for (int i = k; i < m; i++) {
                        t += a.get(i, k) * a.get(i, j);
                    }
                    t = -t / a.get(k, k);
                    for (int i = k; i < m; i++) {
                        a.set(i, j, a.get(i, j) + t * a.get(i, k));
                    }
                }
                e[j] = a.get(k, j);
            }

            if (wantu && k < nct) {
                for (int i = k; i < m; i++) {
                    u[i][k] = a.get(i, k);
                }
            }

            if (k < nrt) {
                e[k] = 0;
                for (int i = k + 1; i < n; i++) {
                    e[k] = MlConstrants.mlMatrixDcuTil.hypotenuse(e[k], e[i]);
                }
                if (e[k] != 0) {
                    if (e[k + 1] < 0) {
                        e[k] = 0 - e[k];
                    }
                    for (int i = k + 1; i < n; i++) {
                        e[i] /= e[k];
                    }
                    e[k + 1] += 1;
                }
                e[k] = -e[k];
                if (k + 1 < m && e[k] != 0) {
                    for (int i = k + 1; i < m; i++) {
                        work[i] = 0.0;
                    }
                    for (int j = k + 1; j < n; j++) {
                        for (int i = k + 1; i < m; i++) {
                            work[i] += e[j] * a.get(i, j);
                        }
                    }
                    for (int j = k + 1; j < n; j++) {
                        t = -e[j] / e[k + 1];
                        for (int i = k + 1; i < m; i++) {
                            a.set(i, j, a.get(i, j) + t * work[i]);
                        }
                    }
                }
                if (wantv) {
                    for (int i = k + 1; i < n; i++) {
                        v[i][k] = e[i];
                    }
                }
            }
        }
        p = Math.min(n, m + 1);
        if (nct < n) {
            s[nct] = a.get(nct, nct);
        }
        if (m < p) {
            s[p - 1] = 0;
        }
        if (nrt + 1 < p) {
            e[nrt] = a.get(nrt, p - 1);
        }
        e[p - 1] = 0;

        if (wantu) {
            for (int j = nct; j < nu; j++) {
                for (int i = 0; i < m; i++) {
                    u[i][j] = 0;
                }
                u[j][j] = 1;
            }
            for (int k = nct - 1; k >= 0; k--) {
                if (s[k] != 0) {
                    for (int j = k + 1; j < nu; j++) {
                        t = 0;
                        for (int i = k; i < m; i++) {
                            t += u[i][k] * u[i][j];
                        }
                        t = -t / u[k][k];
                        for (int i = k; i < m; i++) {
                            u[i][j] += t * u[i][k];
                        }
                    }
                    for (int i = k; i < m; i++) {
                        u[i][k] = -u[i][k];
                    }
                    u[k][k] = 1 + u[k][k];
                    for (int i = 0; i < k - 1; i++) {
                        u[i][k] = 0;
                    }
                } else {
                    for (int i = 0; i < m; i++) {
                        u[i][k] = 0;
                    }
                    u[k][k] = 1;
                }
            }
        }

        if (wantv) {
            for (int k = n - 1; k >= 0; k--) {
                if (k < nrt && e[k] != 0) {
                    for (int j = k + 1; j < n; j++) {
                        t = 0;
                        for (int i = k + 1; i < n; i++) {
                            t += v[i][k] * v[i][j];
                        }
                        t = -t / v[k + 1][k];
                        for (int i = k + 1; i < n; i++) {
                            v[i][j] += t * v[i][k];
                        }
                    }
                }
                for (int i = 0; i < n; i++) {
                    v[i][k] = 0;
                }
                v[k][k] = 1;
            }
        }

        int pp = p - 1;
        int iter = 0;
        double eps = Math.pow(MlConstrants.MLINDEX_BYTES_NUM_2, MlConstrants.MLINDEX_BYTES_NUM_52);
        while (p > 0) {
            int k = p - MlConstrants.MLINDEX_BYTES_NUM_2;

            for (int k1 = k; k1 >= -1; k1--) {
                if (k1 == -1) {
                    break;
                }
                if (Math.abs(e[k1]) <= eps * (Math.abs(s[k1]) + Math.abs(s[k1 + 1]))) {
                    e[k1] = 0;
                    break;
                }
            }
            if (k == p - MlConstrants.MLINDEX_BYTES_NUM_2) {
                kase = MlConstrants.MLINDEX_BYTES_NUM_4;
            } else {
                int ks = p - 1;
                for (int ks1 = ks; ks1 >= k; ks1--) {
                    if (ks1 == k) {
                        break;
                    }
                    t = (ks1 != p ? Math.abs(e[ks1]) : 0) + (ks1 != k + 1 ? Math.abs(e[ks1 - 1]) : 0);
                    if (Math.abs(s[ks1]) <= eps * t) {
                        s[ks1] = 0;
                        break;
                    }
                }
                if (ks == k) {
                    kase = MlConstrants.MLINDEX_BYTES_NUM_3;
                } else if (ks == p - 1) {
                    kase = 1;
                } else {
                    kase = MlConstrants.MLINDEX_BYTES_NUM_2;
                    k = ks;
                }
            }
            k += 1;
            switch (kase) {
                case 1: {
                    f = e[p - MlConstrants.MLINDEX_BYTES_NUM_2];
                    e[p - MlConstrants.MLINDEX_BYTES_NUM_2] = 0;
                    for (int j = p - MlConstrants.MLINDEX_BYTES_NUM_2; j >= k; j--) {
                        t = MlConstrants.mlMatrixDcuTil.hypotenuse(s[j], f);
                        cs = s[j] / t;
                        sn = f / t;
                        s[j] = t;
                        if (j != k) {
                            f = -sn * e[j - 1];
                            e[j - 1] = cs * e[j - 1];
                        }
                        if (wantv) {
                            for (int i = 0; i < n; i++) {
                                t = cs * v[i][j] + sn * v[i][p - 1];
                                v[i][p - 1] = -sn * v[i][j] + cs * v[i][p - 1];
                                v[i][j] = t;
                            }
                        }
                    }
                    break;
                }
                case MlConstrants.MLINDEX_BYTES_NUM_2: {
                    f = e[k - 1];
                    e[k - 1] = 0;
                    for (int j = k; j < p; j++) {
                        t = MlConstrants.mlMatrixDcuTil.hypotenuse(s[j], f);
                        cs = s[j] / t;
                        sn = f / t;
                        s[j] = t;
                        f = -sn * e[j];
                        e[j] = cs * e[j];
                        if (wantu) {
                            for (int i = 0; i < m; i++) {
                                t = cs * u[i][j] + sn * u[i][k - 1];
                                u[i][k - 1] = -sn * u[i][j] + cs * u[i][k - 1];
                                u[i][j] = t;
                            }
                        }
                    }
                    break;
                }
                case MlConstrants.MLINDEX_BYTES_NUM_3: {
                    scale = Math.max(
                            Math.max(Math.max(Math.max(Math.abs(s[p - 1]), Math.abs(s[p - MlConstrants.MLINDEX_BYTES_NUM_2])), Math.abs(e[p - MlConstrants.MLINDEX_BYTES_NUM_2])), Math.abs(s[k])),
                            Math.abs(e[k])
                    );
                    sp = s[p - 1] / scale;
                    spm1 = s[p - MlConstrants.MLINDEX_BYTES_NUM_2] / scale;
                    epm1 = e[p - MlConstrants.MLINDEX_BYTES_NUM_2] / scale;
                    sk = s[k] / scale;
                    ek = e[k] / scale;
                    b = ((spm1 + sp) * (spm1 - sp) + epm1 * epm1) / MlConstrants.MLINDEX_BYTES_NUM_2;
                    c = sp * epm1 * (sp * epm1);
                    shift = 0;
                    if (b != 0 || c != 0) {
                        shift = Math.sqrt(b * b + c);
                        if (b < 0) {
                            shift = -shift;
                        }
                        shift = c / (b + shift);
                    }
                    f = (sk + sp) * (sk - sp) + shift;
                    g = sk * ek;
                    for (int j = k; j < p - 1; j++) {
                        t = MlConstrants.mlMatrixDcuTil.hypotenuse(f, g);
                        cs = f / t;
                        sn = g / t;
                        if (j != k) {
                            e[j - 1] = t;
                        }
                        f = cs * s[j] + sn * e[j];
                        e[j] = cs * e[j] - sn * s[j];
                        g = sn * s[j + 1];
                        s[j + 1] = cs * s[j + 1];
                        if (wantv) {
                            for (int i = 0; i < n; i++) {
                                t = cs * v[i][j] + sn * v[i][j + 1];
                                v[i][j + 1] = -sn * v[i][j] + cs * v[i][j + 1];
                                v[i][j] = t;
                            }
                        }
                        t = MlConstrants.mlMatrixDcuTil.hypotenuse(f, g);
                        cs = f / t;
                        sn = g / t;
                        s[j] = t;
                        f = cs * e[j] + sn * s[j + 1];
                        s[j + 1] = -sn * e[j] + cs * s[j + 1];
                        g = sn * e[j + 1];
                        e[j + 1] = cs * e[j + 1];
                        if (wantu && j < m - 1) {
                            for (int i = 0; i < m; i++) {
                                t = cs * u[i][j] + sn * u[i][j + 1];
                                u[i][j + 1] = -sn * u[i][j] + cs * u[i][j + 1];
                                u[i][j] = t;
                            }
                        }
                    }
                    e[p - MlConstrants.MLINDEX_BYTES_NUM_2] = f;
                    iter = iter + 1;
                    break;
                }
                case MlConstrants.MLINDEX_BYTES_NUM_4: {
                    if (s[k] <= 0) {
                        s[k] = s[k] < 0 ? -s[k] : 0;
                        if (wantv) {
                            for (int i = 0; i <= pp; i++) {
                                v[i][k] = -v[i][k];
                            }
                        }
                    }
                    while (k < pp) {
                        if (s[k] >= s[k + 1]) {
                            break;
                        }
                        t = s[k];
                        s[k] = s[k + 1];
                        s[k + 1] = t;
                        if (wantv && k < n - 1) {
                            for (int i = 0; i < n; i++) {
                                t = v[i][k + 1];
                                v[i][k + 1] = v[i][k];
                                v[i][k] = t;
                            }
                        }
                        if (wantu && k < m - 1) {
                            for (int i = 0; i < m; i++) {
                                t = u[i][k + 1];
                                u[i][k + 1] = u[i][k];
                                u[i][k] = t;
                            }
                        }
                        k += 1;
                    }
                    iter = 0;
                    p -= 1;
                    break;
                }
                default:
                    break;
            }
        }

        if (swapped) {
            double[][] tmp = v;
            v = u;
            u = tmp;
        }
        this.m = m;
        this.n = n;
        this.s = s;
        this.u = u;
        this.v = v;
    }

    public double condition() {
        return this.s[0] / this.s[Math.min(this.m, this.n) - 1];
    }

    public double norm2() {
        return this.s[0];
    }

    public double rank() {
        double eps = Math.pow(MlConstrants.MLINDEX_BYTES_NUM_2, MlConstrants.MLINDEX_BYTES_NUM_52);
        double tol = Math.max(this.m, this.n) * this.s[0] * eps;
        double r = 0.0;
        double[] s = this.s;
        for (int i = 0, ii = s.length; i < ii; i++) {
            if (s[i] > tol) {
                r += 1;
            }
        }
        return r;
    }

    public double[] diagonal() {
        return this.s;
    }

    public double threshold() {
        return (Math.pow(MlConstrants.MLINDEX_BYTES_NUM_2, MlConstrants.MLINDEX_BYTES_NUM_52) / MlConstrants.MLINDEX_BYTES_NUM_2) * Math.max(this.m, this.n) * this.s[0];
    }

    public Object leftSingularVectors() {
        if (!Matrix.isMatrix(this.u)) {
            return new Matrix(this.u, null);
        }
        return (Object) this.u;
    }

    public Object rightSingularVectors() {
        if (!Matrix.isMatrix(this.v)) {
            return new Matrix(this.v, null);
        }
        return this.v;
    }

    public Matrix diagonalMatrix() {
        return Matrix.diag(this.s, null, null);
    }

    public Matrix solve(Matrix value) {
        Matrix y = value;
        double e = this.threshold();
        int scols = this.s.length;
        Matrix ls = Matrix.zeros(scols, scols);

        for (int i = 0; i < scols; i++) {
            if (Math.abs(this.s[i]) <= e) {
                ls.set(i, i, 0);
            } else {
                ls.set(i, i, 1 / this.s[i]);
            }
        }

        double[][] u = (double[][]) this.u;
        Matrix v = (Matrix) this.rightSingularVectors();

        Matrix vl = v.mmul(ls);
        int vRows = v.rows;
        int uRows = u.length;
        Matrix vlu = Matrix.zeros(vRows, uRows);
        double sum;

        for (int i = 0; i < vRows; i++) {
            for (int j = 0; j < uRows; j++) {
                sum = 0;
                for (int k = 0; k < scols; k++) {
                    sum += vl.get(i, k) * u[j][k];
                }
                vlu.set(i, j, sum);
            }
        }

        return vlu.mmul(y);
    }

    public Matrix solveForDiagonal(double[] value) {
        return this.solve(Matrix.diag(value, null, null));
    }

    public Matrix inverse() {
        double[] v = (double[]) this.v;
        double e = this.threshold();
        int vRows = v.length;
        int vCols = v.length;
        Matrix x = new Matrix(vRows, this.s.length);

        for (int i = 0; i < vRows; i++) {
            for (int j = 0; j < vCols; j++) {
                if (Math.abs(this.s[j]) > e) {
                    x.set(i, j, v[i] / this.s[j]);
                } else {
                    x.set(i, j, 0);
                }
            }
        }

        double[][] u = (double[][]) this.u;

        int uRows = u.length;
        int uCols = u[0].length;
        Matrix y = new Matrix(vRows, uRows);
        double sum;

        for (int i = 0; i < vRows; i++) {
            for (int j = 0; j < uRows; j++) {
                sum = 0.0;
                for (int k = 0; k < uCols; k++) {
                    sum += x.get(i, k) * u[j][k];
                }
                y.set(i, j, sum);
            }
        }
        return y;
    }
}

// ml-matrix src/abstractMatrix.js
class Matrix {
    public int rows = 0;
    public int columns = 0;

    public double[][] array ;


    Matrix(Object nRows, Object nColumns) {
        if (nRows instanceof Matrix) {
            array = ((Matrix) nRows).array;
            rows = ((Matrix) nRows).rows;
            columns = ((Matrix) nRows).columns;
        } else if ((nRows instanceof Integer) && (int) nRows > 0) {
            this.array = new double[(int) nRows][];
            if ((int) nColumns > 0) {
                for (int i = 0; i < (int) nRows; i++) {
                    double[] arr = new double[(int) nColumns];
                    Arrays.fill(arr, 0.0);
                    this.array[i] = arr;
                }
                rows = (int) nRows;
                columns = (int) nColumns;
            } else {
                throw new Error("nColumns must be a positive integer");
            }

        } else if (nRows instanceof double[][] && ((double[][]) nRows).length > 0 && ((double[][]) nRows)[0] instanceof double[]) {
            array = (double[][]) nRows;
            rows = ((double[][]) nRows).length;
            columns = ((double[][]) nRows)[0].length;
        } else {
            throw new Error("First argument must be a positive number or an array");
        }

    }

    public Matrix set(int rowIndex, int columnIndex, double value) {
        this.array[rowIndex][columnIndex] = value;
        return this;
    }

    public double get(int rowIndex, int columnIndex) {
        return this.array[rowIndex][columnIndex];
    }


    public Matrix clone() {
        Matrix newMatrix = new Matrix(rows, columns);
        for (int row = 0; row < this.rows; row++) {
            for (int column = 0; column < this.columns; column++) {
                newMatrix.set(row, column, get(row, column));
            }
        }
        return newMatrix;
    }

    public Matrix from1DArray(int newRows, int newColumns, double[] newData) {

        int length = newRows * newColumns;
        if (length != newData.length) {
            throw new RangeError("\"Data length does not match given dimensions\"");
        }
        Matrix newMatrix = new Matrix(newRows, newColumns);
        for (int row = 0; row < newRows; row++) {
            for (int column = 0; column < newColumns; column++) {
                newMatrix.set(row, column, newData[row * newColumns + column]);
            }
        }
        return newMatrix;
    }

    public static Matrix rowVector(double[] newData) {
        Matrix vector = new Matrix(1, newData.length);
        for (int i = 0; i < newData.length; i++) {
            vector.set(0, i, newData[i]);
        }
        return vector;
    }

    public static Matrix columnVector(double[] newData) {
        Matrix vector = new Matrix(newData.length, 1);
        for (int i = 0; i < newData.length; i++) {
            vector.set(i, 0, newData[i]);
        }
        return vector;
    }

    public static Matrix empty(int rows, int columns) {
        return new Matrix(rows, columns);
    }

    public static Matrix zeros(int rows, int columns) {
        return Matrix.empty(rows, columns).fill(0.0);
    }

    public static Matrix ones(int rows, int columns) {
        return Matrix.empty(rows, columns).fill(1.0);
    }

    public static Matrix rand(int rows, int columns, Object rng) {
        Matrix matrix = empty(rows, columns);
        for (int i = 0; i < rows; i++) {
            for (int j = 0; j < columns; j++) {
              matrix.set(i, j, Math.random());
            }
        }
        return matrix;
    }

    public Matrix randInt(int rows, int columns, Integer maxValue, double rng) {

        int maxV = maxValue != null ? maxValue : MlConstrants.MLINDEX_BYTES_NUM_1000;
        Matrix matrix = empty(rows, columns);
        for (int i = 0; i < this.rows; i++) {
            for (int j = 0; j < this.columns; j++) {
                double value = Math.floor(Math.random() * maxV);
                matrix.set(i, j, value);
            }
        }
        return matrix;
    }

    public static Matrix eye(int rows, Integer columns, Integer value) {
        Integer newColumns = columns;
        Integer newValue = value;
        if (columns == null) {
            newColumns = rows;
        }
        if (value == null) {
            newValue = 1;
        }
        double min = Math.min(rows, newColumns);

        Matrix matrix = zeros(rows, newColumns);
        for (int i = 0; i < min; i++) {
            matrix.set(i, i, newValue);
        }
        return matrix;
    }

    public static Matrix diag(double[] data, Integer rows, Integer columns) {
        Integer newRows = rows;
        Integer newColumns = columns;
        int l = data.length;
        if (rows == null) {
            newRows = l;
        }
        if (columns == null) {
            newColumns = rows;
        }
        int min = Math.min(Math.min(l, newRows), newColumns);

        Matrix matrix = zeros(newRows, newColumns);
        for (int i = 0; i < min; i++) {
            matrix.set(i, i, data[i]);
        }
        return matrix;
    }

    public Matrix min(Matrix matrix1, Matrix matrix2) {
        Matrix matrix11 = checkMatrix(matrix1);
        Matrix matrix22 = checkMatrix(matrix2);
        int rows = matrix1.rows;
        int columns = matrix1.columns;
        Matrix result = new Matrix(rows, columns);
        for (int i = 0; i < rows; i++) {
            for (int j = 0; j < columns; j++) {
                result.set(i, j, Math.min(matrix11.get(i, j), matrix22.get(i, j)));
            }
        }
        return result;
    }

    public Matrix max(Matrix matrix1, Matrix matrix2) {
        Matrix matrix11 = checkMatrix(matrix1);
        Matrix matrix22 = checkMatrix(matrix2);
        int rows = matrix11.rows;
        int columns = matrix11.columns;
        Matrix result = new Matrix(rows, columns);
        for (int i = 0; i < rows; i++) {
            for (int j = 0; j < columns; j++) {
                result.set(i, j, Math.max(matrix11.get(i, j), matrix22.get(i, j)));
            }
        }
        return result;
    }

    public static Matrix checkMatrix(Object value) {
        return Matrix.isMatrix(value) ? (Matrix) value : new Matrix(value, null);
    }

    public static boolean isMatrix(Object value) {
        return value != null && (value instanceof Matrix);
    }

    public int size() {

        return this.rows * this.columns;
    }

    public Matrix apply(ActivationFunction<Matrix, Integer, Integer> callback) {
        int ii = this.rows;
        int jj = this.columns;
        for (int i = 0; i < ii; i++) {
            for (int j = 0; j < jj; j++) {
                callback.apply(this, i, j);
            }
        }
        return this;
    }

    public double[] to1dArray() {

        double[] array = new double[size()];
        Arrays.fill(array, 0.0);
        for (int i = 0; i < this.rows; i++) {
            for (int j = 0; j < this.columns; j++) {
                array[i * this.columns + j] = get(i, j);
            }
        }
        return array;
    }

    public double[][] to2dArray() {
        double[][] copy = new double[this.rows][];

        for (int i = 0; i < this.rows; i++) {
            double[] arr = new double[this.columns];
            Arrays.fill(arr, 0.0);
            copy[i] = arr;
            for (int j = 0; j < this.columns; j++) {
                copy[i][j] = this.get(i, j);
            }
        }
        return copy;
    }

    public boolean isRowVector() {
        return this.rows == 1;
    }

    public boolean isColumnVector() {
        return this.columns == 1;
    }

    public boolean isVector() {
        return this.rows == 1 || this.columns == 1;
    }

    public boolean isSquare() {
        return this.rows == this.columns;
    }

    public boolean isSymmetric() {
        if (isSquare()) {
            for (int i = 0; i < this.rows; i++) {
                for (int j = 0; j < i; j++) {
                    if (get(i, j) != get(j, i)) {
                        return false;
                    }
                }
            }
            return true;
        }
        return false;
    }

    public Matrix repeat(int rowRep, int colRep) {
        int newRowRep = rowRep > 0 ? rowRep : 1;
        int newColRep = colRep > 0 ? colRep : 1;
        Matrix matrix = new Matrix(this.rows * newRowRep, this.columns * newColRep);

        for (int i = 0; i < newRowRep; i++) {
            for (int j = 0; j < newColRep; j++) {
                matrix.setSubMatrix(this, this.rows * i, this.columns * j);
            }
        }
        return matrix;
    }

    public Matrix fill(double value) {
        for (int i = 0; i < this.rows; i++) {
            for (int j = 0; j < this.columns; j++) {
                this.set(i, j, value);
            }
        }
        return this;
    }

    public Matrix neg() {
        return this.mulS(-1);
    }

    public double[] getRow(int index) {
        MlConstrants.mlMatrixUtil.checkRowIndex(this, index, false);
        double[] row = new double[this.columns];
        Arrays.fill(row, 0.0);

        for (int i = 0; i < this.columns; i++) {
            row[i] = this.get(index, i);
        }
        return row;
    }

    public Matrix getRowVector(int index) {
        return Matrix.rowVector(getRow(index));
    }

    public Matrix setRow(int index, Matrix array) {
        MlConstrants.mlMatrixUtil.checkRowIndex(this, index, false);
        for (int i = 0; i < this.columns; i++) {
            set(index, i, array.get(i, i));
        }
        return this;
    }

    public Matrix swapRows(int row1, int row2) {
        MlConstrants.mlMatrixUtil.checkRowIndex(this, row1, false);
        MlConstrants.mlMatrixUtil.checkRowIndex(this, row2, false);
        for (int i = 0; i < this.columns; i++) {
            double temp = this.get(row1, i);
            this.set(row1, i, this.get(row2, i));
            this.set(row2, i, temp);
        }
        return this;
    }

    public double[] getColumn(int index) {
        MlConstrants.mlMatrixUtil.checkColumnIndex(this, index, false);
        double[] column = new double[this.rows];
        Arrays.fill(column, 0.0);
        for (int i = 0; i < this.rows; i++) {
            column[i] = this.get(i, index);
        }
        return column;
    }

    public Matrix getColumnVector(int index) {
        return Matrix.columnVector(this.getColumn(index));
    }

    public Matrix setColumn(int index, double[] array) {
        MlConstrants.mlMatrixUtil.checkColumnIndex(this, index, false);
        for (int i = 0; i < this.rows; i++) {
            this.set(i, index, array[i]);
        }
        return this;
    }

    public Matrix swapColumns(int column1, int column2) {
        MlConstrants.mlMatrixUtil.checkColumnIndex(this, column1, false);
        MlConstrants.mlMatrixUtil.checkColumnIndex(this, column2, false);
        for (int i = 0; i < this.rows; i++) {
            double temp = this.get(i, column1);
            this.set(i, column1, this.get(i, column2));
            this.set(i, column2, temp);
        }
        return this;
    }

    public Matrix addRowVector(Matrix vector) {
        double[] newVector = MlConstrants.mlMatrixUtil.checkRowVector(this, vector);
        for (int i = 0; i < this.rows; i++) {
            for (int j = 0; j < this.columns; j++) {
                this.set(i, j, this.get(i, j) + newVector[j]);
            }
        }
        return this;
    }

    public Matrix subRowVector(double[] vector) {
        double[] newVector = MlConstrants.mlMatrixUtil.checkRowVector(this, vector);
        for (int i = 0; i < this.rows; i++) {
            for (int j = 0; j < this.columns; j++) {
                this.set(i, j, this.get(i, j) - newVector[j]);
            }
        }
        return this;
    }

    public Matrix mulRowVector(double[] vector) {
        double[] newVector = MlConstrants.mlMatrixUtil.checkRowVector(this, vector);
        for (int i = 0; i < rows; i++) {
            for (int j = 0; j < columns; j++) {
                this.set(i, j, this.get(i, j) * newVector[j]);
            }
        }
        return this;
    }

    public Matrix divRowVector(double[] vector) {
        double[] newVector = MlConstrants.mlMatrixUtil.checkRowVector(this, vector);

        for (int i = 0; i < this.rows; i++) {
            for (int j = 0; j < this.columns; j++) {
                this.set(i, j, this.get(i, j) / newVector[j]);
            }
        }
        return this;
    }

    public Matrix addColumnVector(double[] vector) {
        double[] newVector = MlConstrants.mlMatrixUtil.checkRowVector(this, vector);

        for (int i = 0; i < this.rows; i++) {
            for (int j = 0; j < this.columns; j++) {
                this.set(i, j, this.get(i, j) + newVector[i]);
            }
        }
        return this;
    }

    public Matrix subColumnVector(double[] vector) {
        double[] newVector = MlConstrants.mlMatrixUtil.checkColumnVector(this, vector);
        for (int i = 0; i < this.rows; i++) {
            for (int j = 0; j < this.columns; j++) {
                this.set(i, j, this.get(i, j) - newVector[i]);
            }
        }
        return this;
    }

    public Matrix mulColumnVector(double[] vector) {
        double[] newVector = MlConstrants.mlMatrixUtil.checkColumnVector(this, vector);
        for (int i = 0; i < this.rows; i++) {
            for (int j = 0; j < this.columns; j++) {
                this.set(i, j, this.get(i, j) * newVector[i]);
            }
        }
        return this;
    }

    public Matrix divColumnVector(Matrix vector) {
        double[] newVector = MlConstrants.mlMatrixUtil.checkColumnVector(this, vector);
        for (int i = 0; i < this.rows; i++) {
            for (int j = 0; j < this.columns; j++) {
                this.set(i, j, this.get(i, j) / newVector[i]);
            }
        }
        return this;
    }

    public Matrix mulRow(int index, double value) {
        MlConstrants.mlMatrixUtil.checkRowIndex(this, index, false);
        for (int i = 0; i < this.columns; i++) {
            this.set(index, i, this.get(index, i) * value);
        }
        return this;
    }

    public Matrix mulColumn(int index, double value) {
        MlConstrants.mlMatrixUtil.checkColumnIndex(this, index, false);
        for (int i = 0; i < this.rows; i++) {
            this.set(i, index, this.get(i, index) * value);
        }
        return this;
    }

    public double max() {
        double v = this.get(0, 0);

        for (int i = 0; i < this.rows; i++) {
            for (int j = 0; j < this.columns; j++) {
                if (this.get(i, j) > v) {
                    v = this.get(i, j);
                }
            }
        }
        return v;
    }

    public int[] maxIndex() {
        double v = this.get(0, 0);
        int[] idx = {0, 0};
        for (int i = 0; i < this.rows; i++) {
            for (int j = 0; j < this.columns; j++) {
                if (this.get(i, j) > v) {
                    v = this.get(i, j);
                    idx[0] = i;
                    idx[1] = j;
                }
            }
        }
        return idx;
    }

    public double min() {
        double v = this.get(0, 0);
        for (int i = 0; i < this.rows; i++) {
            for (int j = 0; j < this.columns; j++) {
                if (this.get(i, j) < v) {
                    v = this.get(i, j);
                }
            }
        }
        return v;
    }

    public int[] minIndex() {
        double v = this.get(0, 0);
        int[] idx = {0, 0};
        for (int i = 0; i < this.rows; i++) {
            for (int j = 0; j < this.columns; j++) {
                if (this.get(i, j) < v) {
                    v = this.get(i, j);
                    idx[0] = i;
                    idx[1] = j;
                }
            }
        }
        return idx;
    }

    public double maxRow(int row) {
        MlConstrants.mlMatrixUtil.checkRowIndex(this, row, false);
        double v = get(row, 0);
        for (int i = 1; i < this.columns; i++) {
            if (this.get(row, i) > v) {
                v = this.get(row, i);
            }
        }
        return v;
    }

    public int[] maxRowIndex(int row) {
        MlConstrants.mlMatrixUtil.checkRowIndex(this, row, false);
        double v = this.get(row, 0);
        int[] idx = {row, 0};
        for (int i = 1; i < this.columns; i++) {
            if (this.get(row, i) > v) {
                v = this.get(row, i);
                idx[1] = i;
            }
        }
        return idx;
    }

    public double minRow(int row) {
        MlConstrants.mlMatrixUtil.checkRowIndex(this, row, false);
        double v = this.get(row, 0);
        for (int i = 1; i < this.columns; i++) {
            if (this.get(row, i) < v) {
                v = this.get(row, i);
            }
        }
        return v;
    }

    public int[] minRowIndex(int row) {
        MlConstrants.mlMatrixUtil.checkRowIndex(this, row, false);
        double v = this.get(row, 0);
        int[] idx = {row, 0};
        for (int i = 1; i < this.columns; i++) {
            if (this.get(row, i) < v) {
                v = this.get(row, i);
                idx[1] = i;
            }
        }
        return idx;
    }

    public double maxColumn(int column) {
        MlConstrants.mlMatrixUtil.checkColumnIndex(this, column, false);
        double v = this.get(0, column);
        for (int i = 1; i < this.rows; i++) {
            if (this.get(i, column) > v) {
                v = this.get(i, column);
            }
        }
        return v;
    }

    public int[] maxColumnIndex(int column) {
        MlConstrants.mlMatrixUtil.checkColumnIndex(this, column, false);
        double v = this.get(0, column);
        int[] idx = {0, column};
        for (int i = 1; i < this.rows; i++) {
            if (this.get(i, column) > v) {
                v = this.get(i, column);
                idx[0] = i;
            }
        }
        return idx;
    }

    public double minColumn(int column) {
        MlConstrants.mlMatrixUtil.checkColumnIndex(this, column, false);
        double v = this.get(0, column);
        for (int i = 1; i < this.rows; i++) {
            if (this.get(i, column) < v) {
                v = this.get(i, column);
            }
        }
        return v;
    }

    public int[] minColumnIndex(int column) {
        MlConstrants.mlMatrixUtil.checkColumnIndex(this, column, false);
        double v = this.get(0, column);
        int[] idx = {0, column};
        for (int i = 1; i < this.rows; i++) {
            if (this.get(i, column) < v) {
                v = this.get(i, column);
                idx[0] = i;
            }
        }
        return idx;
    }

    public double[] diag() {
        int min = Math.min(rows, columns);
        double[] diag = new double[min];
        Arrays.fill(diag, 0.0);
        for (int i = 0; i < min; i++) {
            diag[i] = this.get(i, i);
        }
        return diag;
    }

    public Object sum(String by) {
        switch (by) {
            case "row":
                return MlConstrants.mlMatrixUtil.sumByRow(this);
            case "column":
                return MlConstrants.mlMatrixUtil.sumByColumn(this);
            default:
                return MlConstrants.mlMatrixUtil.sumAll(this);
        }
    }

    public double mean() {
        return ((double) sum("") / size());
    }

    public double prod() {
        double prod = 1.0;
        for (int i = 0; i < this.rows; i++) {
            for (int j = 0; j < this.columns; j++) {
                prod *= this.get(i, j);
            }
        }
        return prod;
    }

    public Matrix cumulativeSum() {
        double sum = 0.0;
        for (int i = 0; i < this.rows; i++) {
            for (int j = 0; j < this.columns; j++) {
                sum += this.get(i, j);
                this.set(i, j, sum);
            }
        }
        return this;

    }

    public double dot(Matrix vector2) {
        double[] vector2D1 = new double[0];
        if (Matrix.isMatrix(vector2)) {
            vector2D1 = to1dArray();
        }
        double[] vector1D1 = to1dArray();
        if (vector1D1.length != vector2D1.length) {
            throw new RangeError("vectors do not have the same size");
        }
        double dot = 0.0;
        for (int i = 0; i < vector1D1.length; i++) {
            dot += vector1D1[i] * vector2D1[i];
        }
        return dot;
    }

    public Matrix mmul(Matrix other) {
        Matrix newOther = Matrix.checkMatrix(other);
        if (this.columns != newOther.rows) {
            throw new RangeError("Number of columns of left matrix are not equal to number of rows of right matrix.");
        }
        int m = rows;
        int n = columns;
        int p = newOther.columns;

        Matrix result = new Matrix(m, p);

        double[] bColj = new double[n];
        Arrays.fill(bColj, 0.0);

        for (int j = 0; j < p; j++) {
            for (int k = 0; k < n; k++) {
                bColj[k] = newOther.get(k, j);
            }
            for (int i = 0; i < m; i++) {
                double s = 0.0;
                for (int k = 0; k < n; k++) {
                    s += this.get(i, k) * bColj[k];
                }
                result.set(i, j, s);
            }
        }
        return result;
    }

    public Matrix strassen2x2(Matrix other) {
        Matrix result = new Matrix(MlConstrants.MLINDEX_BYTES_NUM_2, MlConstrants.MLINDEX_BYTES_NUM_2);
        double a11 = this.get(0, 0);
        double b11 = other.get(0, 0);
        double a12 = this.get(0, 1);
        double b12 = other.get(0, 1);
        double a21 = this.get(1, 0);
        double b21 = other.get(1, 0);
        double a22 = this.get(1, 1);
        double b22 = other.get(1, 1);

        double m1 = (a11 + a22) * (b11 + b22);
        double m2 = (a21 + a22) * b11;
        double m3 = a11 * (b12 - b22);
        double m4 = a22 * (b21 - b11);
        double m5 = (a11 + a12) * b22;
        double m6 = (a21 - a11) * (b11 + b12);
        double m7 = (a12 - a22) * (b21 + b22);

        double c00 = m1 + m4 - m5 + m7;
        double c01 = m3 + m5;
        double c10 = m2 + m4;
        double c11 = m1 - m2 + m3 + m6;

        result.set(0, 0, c00);
        result.set(0, 1, c01);
        result.set(1, 0, c10);
        result.set(1, 1, c11);
        return result;
    }

    public Matrix strassen3x3(Matrix other) {
        Matrix result = new Matrix(MlConstrants.MLINDEX_BYTES_NUM_3, MlConstrants.MLINDEX_BYTES_NUM_3);

        double a00 = this.get(0, 0);
        double a01 = this.get(0, 1);
        double a02 = this.get(0, MlConstrants.MLINDEX_BYTES_NUM_2);
        double a10 = this.get(1, 0);
        double a11 = this.get(1, 1);
        double a12 = this.get(1, MlConstrants.MLINDEX_BYTES_NUM_2);
        double a20 = this.get(MlConstrants.MLINDEX_BYTES_NUM_2, 0);
        double a21 = this.get(MlConstrants.MLINDEX_BYTES_NUM_2, 1);
        double a22 = this.get(MlConstrants.MLINDEX_BYTES_NUM_2, MlConstrants.MLINDEX_BYTES_NUM_2);

        double b00 = other.get(0, 0);
        double b01 = other.get(0, 1);
        double b02 = other.get(0, MlConstrants.MLINDEX_BYTES_NUM_2);
        double b10 = other.get(1, 0);
        double b11 = other.get(1, 1);
        double b12 = other.get(1, MlConstrants.MLINDEX_BYTES_NUM_2);
        double b20 = other.get(MlConstrants.MLINDEX_BYTES_NUM_2, 0);
        double b21 = other.get(MlConstrants.MLINDEX_BYTES_NUM_2, 1);
        double b22 = other.get(MlConstrants.MLINDEX_BYTES_NUM_2, MlConstrants.MLINDEX_BYTES_NUM_2);

        double m1 = (a00 + a01 + a02 - a10 - a11 - a21 - a22) * b11;
        double m2 = (a00 - a10) * (-b01 + b11);
        double m3 = a11 * (-b00 + b01 + b10 - b11 - b12 - b20 + b22);
        double m4 = (-a00 + a10 + a11) * (b00 - b01 + b11);
        double m5 = (a10 + a11) * (-b00 + b01);
        double m6 = a00 * b00;
        double m7 = (-a00 + a20 + a21) * (b00 - b02 + b12);
        double m8 = (-a00 + a20) * (b02 - b12);
        double m9 = (a20 + a21) * (-b00 + b02);
        double m10 = (a00 + a01 + a02 - a11 - a12 - a20 - a21) * b12;
        double m11 = a21 * (-b00 + b02 + b10 - b11 - b12 - b20 + b21);
        double m12 = (-a02 + a21 + a22) * (b11 + b20 - b21);
        double m13 = (a02 - a22) * (b11 - b21);
        double m14 = a02 * b20;
        double m15 = (a21 + a22) * (-b20 + b21);
        double m16 = (-a02 + a11 + a12) * (b12 + b20 - b22);
        double m17 = (a02 - a12) * (b12 - b22);
        double m18 = (a11 + a12) * (-b20 + b22);
        double m19 = a01 * b10;
        double m20 = a12 * b21;
        double m21 = a10 * b02;
        double m22 = a20 * b01;
        double m23 = a22 * b22;

        double c00 = m6 + m14 + m19;
        double c01 = m1 + m4 + m5 + m6 + m12 + m14 + m15;
        double c02 = m6 + m7 + m9 + m10 + m14 + m16 + m18;
        double c10 = m2 + m3 + m4 + m6 + m14 + m16 + m17;
        double c11 = m2 + m4 + m5 + m6 + m20;
        double c12 = m14 + m16 + m17 + m18 + m21;
        double c20 = m6 + m7 + m8 + m11 + m12 + m13 + m14;
        double c21 = m12 + m13 + m14 + m15 + m22;
        double c22 = m6 + m7 + m8 + m9 + m23;

        result.set(0, 0, c00);
        result.set(0, 1, c01);
        result.set(0, MlConstrants.MLINDEX_BYTES_NUM_2, c02);
        result.set(1, 0, c10);
        result.set(1, 1, c11);
        result.set(1, MlConstrants.MLINDEX_BYTES_NUM_2, c12);
        result.set(MlConstrants.MLINDEX_BYTES_NUM_2, 0, c20);
        result.set(MlConstrants.MLINDEX_BYTES_NUM_2, 1, c21);
        result.set(MlConstrants.MLINDEX_BYTES_NUM_2, MlConstrants.MLINDEX_BYTES_NUM_2, c22);
        return result;
    }

    public Matrix mmulStrassen(Matrix y) {
        Matrix x = clone();
        int r1 = x.rows;
        int c1 = x.columns;
        int r2 = y.rows;
        int c2 = y.columns;
        if (c1 != r2) {
            throw new RangeError("\"Multiplying \\(r1) x \\(c1) and \\(r2) x \\(c2) matrix: dimensions do not match.\"");
        }

        int r = Math.max(r1, r2);
        int c = Math.max(c1, c2);
        x = this.embed(x, r, c);
        Matrix y1 = this.embed(y, r, c);
        return this.blockMult(x, y1, r, c);
    }

    public Matrix embed(Matrix mat, int rows, int cols) {
        int r = mat.rows;
        int c = mat.columns;
        if (r == rows && c == cols) {
            return mat;
        } else {
            Matrix resultat = Matrix.zeros(rows, cols);
            resultat = resultat.setSubMatrix(mat, 0, 0);
            return resultat;
        }
    }

    public Matrix blockMult(Matrix a, Matrix b, int rows, int cols) {
        Matrix newA = a;
        Matrix newB = b;
        if (rows <= MlConstrants.MLINDEX_BYTES_NUM_512 || cols <= MlConstrants.MLINDEX_BYTES_NUM_512) {
            return newA.mmul(newB);
        }

        if (rows % MlConstrants.MLINDEX_BYTES_NUM_2 == 1 && cols % MlConstrants.MLINDEX_BYTES_NUM_2 == 1) {
            newA = this.embed(newA, rows + 1, cols + 1);
            newB = this.embed(newB, rows + 1, cols + 1);
        } else if (rows % MlConstrants.MLINDEX_BYTES_NUM_2 == 1) {
            newA = this.embed(newA, rows + 1, cols);
            newB = this.embed(newB, rows + 1, cols);
        } else if (cols % MlConstrants.MLINDEX_BYTES_NUM_2 == 1) {
            newA = this.embed(newA, rows, cols + 1);
            newB = this.embed(newB, rows, cols + 1);
        }

        int halfRows = newA.rows / MlConstrants.MLINDEX_BYTES_NUM_2;
        int halfCols = newA.columns / MlConstrants.MLINDEX_BYTES_NUM_2;
        Matrix a11 = newA.subMatrix(0, halfRows - 1, 0, halfCols - 1);
        Matrix b11 = newA.subMatrix(0, halfRows - 1, 0, halfCols - 1);

        Matrix a12 = newA.subMatrix(0, halfRows - 1, halfCols, newA.columns - 1);
        Matrix b12 = newB.subMatrix(0, halfRows - 1, halfCols, newB.columns - 1);

        Matrix a21 = newA.subMatrix(halfRows, newA.rows - 1, 0, halfCols - 1);
        Matrix b21 = newB.subMatrix(halfRows, newB.rows - 1, 0, halfCols - 1);

        Matrix a22 = newA.subMatrix(halfRows, newA.rows - 1, halfCols, newA.columns - 1);
        Matrix b22 = newB.subMatrix(halfRows, newB.rows - 1, halfCols, newB.columns - 1);

        Matrix m1 = this.blockMult(Matrix.add(a11, a22), Matrix.add(b11, b22), halfRows, halfCols);
        Matrix m2 = this.blockMult(Matrix.add(a21, a22), b11, halfRows, halfCols);
        Matrix m3 = this.blockMult(a11, Matrix.sub(b12, b22), halfRows, halfCols);
        Matrix m4 = this.blockMult(a22, Matrix.sub(b21, b11), halfRows, halfCols);
        Matrix m5 = this.blockMult(Matrix.add(a11, a12), b22, halfRows, halfCols);
        Matrix m6 = this.blockMult(Matrix.sub(a21, a11), Matrix.add(b11, b12), halfRows, halfCols);
        Matrix m7 = this.blockMult(Matrix.sub(a12, a22), Matrix.add(b21, b22), halfRows, halfCols);

        Matrix c11 = Matrix.add(m1, m4);
        c11.sub(m5);
        c11.add(m7);
        Matrix c12 = Matrix.add(m3, m5);
        Matrix c21 = Matrix.add(m2, m4);
        Matrix c22 = Matrix.sub(m1, m2);
        c22.add(m3);
        c22.add(m6);

        Matrix resultat = Matrix.zeros(MlConstrants.MLINDEX_BYTES_NUM_2 * c11.rows, MlConstrants.MLINDEX_BYTES_NUM_2 * c11.columns);
        resultat = resultat.setSubMatrix(c11, 0, 0);
        resultat = resultat.setSubMatrix(c12, c11.rows, 0);
        resultat = resultat.setSubMatrix(c21, 0, c11.columns);
        resultat = resultat.setSubMatrix(c22, c11.rows, c11.columns);
        return resultat.subMatrix(0, rows - 1, 0, cols - 1);
    }

    public Matrix scaleRows(Integer min, Integer max) {
        if (min >= max) {
            throw new RangeError("min should be strictly smaller than max");
        }
        MlOptions options = new MlOptions();
        options.min = min;
        options.max = max;
        Matrix newMatrix = new Matrix(rows, columns);

        for (int i = 0; i < this.rows; i++) {
            Object scaled = MlArrayUtilsArrayUtilsCls.scale(this.getRow(i), options);
            newMatrix.setRow(i, (Matrix) scaled);
        }
        return newMatrix;
    }

    public Matrix scaleColumns(Integer min, Integer max) {

        if (min >= max) {
            throw new RangeError("min should be strictly smaller than max");
        }
        Matrix newMatrix = Matrix.empty(rows, columns);
        MlOptions options = new MlOptions();
        options.min = min;
        options.max = max;
        for (int i = 0; i < this.columns; i++) {
            Object scaled = MlArrayUtilsArrayUtilsCls.scale(this.getColumn(i), options);
            newMatrix.setColumn(i, (double[]) scaled);
        }
        return newMatrix;
    }

    public Matrix kroneckerProduct(Matrix other) {
        Matrix other1 = Matrix.checkMatrix(other);

        int m = rows;
        int n = columns;
        int p = other.rows;
        int q = other.columns;

        Matrix result = new Matrix(m * p, n * q);

        for (int i = 0; i < m; i++) {
            for (int j = 0; j < n; j++) {
                for (int k = 0; k < p; k++) {
                    for (int l = 0; l < q; l++) {
                        result.set(p * i + k, q * j + l, this.get(i, j) * other.get(k, l));
                    }
                }
            }
        }
        return result;
    }

    public Matrix transpose() {
        Matrix result = new Matrix(this.columns, this.rows);
        for (int i = 0; i < this.rows; i++) {
            for (int j = 0; j < this.columns; j++) {
                result.set(j, i, this.get(i, j));
            }
        }
        return result;
    }

    public Matrix sortRows(CompareFunction compareFunction) {
        return this;
    }


    public Matrix sortColumns() {
        return this;
    }

    public Matrix subMatrix(int startRow, int endRow, int startColumn, int endColumn) {
        MlConstrants.mlMatrixUtil.checkRange(this, startRow, endRow, startColumn, endColumn);
        Matrix newMatrix = new Matrix(endRow - startRow + 1, endColumn - startColumn + 1);

        for (int i = startRow; i <= endRow; i++) {
            for (int j = startColumn; j <= endColumn; j++) {
                newMatrix.set(i - startRow, j - startColumn, this.get(i, j));
            }
        }
        return newMatrix;
    }

    public Matrix subMatrixRow(int[] indices, int startColumn, Integer endColumn) {

        Integer newEndColumn = endColumn;
        if (newEndColumn == null) {
            newEndColumn = this.columns - 1;
        }

        if (startColumn > newEndColumn || startColumn < 0 || startColumn >= columns || newEndColumn < 0 || newEndColumn >= columns) {
            throw new RangeError("Argument out of range");
        }

        Matrix newMatrix = new Matrix(indices.length, newEndColumn - startColumn + 1);

        for (int i = 0; i < indices.length; i++) {
            for (int j = startColumn; j < newEndColumn; j++) {
                if (indices[i] < 0 || indices[i] >= this.rows) {
                    throw new RangeError("Row index out of range:" + indices[i]);
                }
                newMatrix.set(i, j - startColumn, this.get(indices[i], j));
            }
        }
        return newMatrix;
    }

    public Matrix subMatrixColumn(int[] indices, int startRow, Integer endRow) {

        Integer newEndRow = endRow;
        if (newEndRow == null) {
            newEndRow = this.rows - 1;
        }
        if (startRow > newEndRow || startRow < 0 || startRow >= rows || newEndRow < 0 || newEndRow >= rows) {
            throw new RangeError("Argument out of range");
        }

        Matrix newMatrix = new Matrix(newEndRow - startRow + 1, indices.length);

        for (int i = 0; i < indices.length; i++) {
            for (int j = startRow; j < newEndRow; j++) {
                if (indices[i] < 0 || indices[i] >= this.columns) {
                    throw new RangeError("Column index out of range:" + indices[i]);
                }
                newMatrix.set(j - startRow, i, this.get(j, indices[i]));
            }
        }
        return newMatrix;
    }

    public Matrix setSubMatrix(Matrix matrix, int startRow, int startColumn) {
        Matrix matrix1 = Matrix.checkMatrix(matrix);
        int endRow = startRow + matrix.rows - 1;
        int endColumn = startColumn + matrix.columns - 1;
        MlConstrants.mlMatrixUtil.checkRange(this, startRow, endRow, startColumn, endColumn);

        for (int i = 0; i < matrix1.rows; i++) {
            for (int j = 0; j < matrix1.columns; j++) {
                matrix.set(startRow + i, startColumn + j, matrix.get(i, j));
            }
        }
        return this;
    }

    public Matrix selection(int[] rowIndices, int[] columnIndices) {
        MlConstrants.mlMatrixUtil.checkIndices(this, rowIndices, columnIndices);
        Matrix newMatrix = new Matrix(rowIndices.length, columnIndices.length);
        return newMatrix;
    }

    public double trace() {
        int min = Math.min(this.rows, this.columns);
        double trace = 0.0;
        for (int i = 0; i < min; i++) {
            trace += this.get(i, i);
        }
        return trace;
    }

    public MlMatrixTransposeView transposeView() {
        return new MlMatrixTransposeView(this);
    }

    public MlMatrixRowView rowView(int row) {
        MlConstrants.mlMatrixUtil.checkRowIndex(this, row, false);
        return new MlMatrixRowView(this, row);
    }

    public MlMatrixColumnView columnView(int column) {
        MlConstrants.mlMatrixUtil.checkColumnIndex(this, column, false);
        return new MlMatrixColumnView(this, column);
    }

    public MlMatrixFlipRowView flipRowView() {

        return new MlMatrixFlipRowView(this);
    }

    public MlMatrixFlipColumnView flipColumnView() {
        return new MlMatrixFlipColumnView(this);
    }

    public MlMatrixSubView subMatrixView(int startRow, int endRow, int startColumn, int endColumn) {
        return new MlMatrixSubView(this, startRow, endRow, startColumn, endColumn);
    }

    public MlMatrixSelectionView selectionView(int[] rowIndices, int[] columnIndices) {
        return new MlMatrixSelectionView(this, rowIndices, columnIndices);
    }

    public double det() {
        if (isSquare()) {
            double a;
            double b;
            double c;
            double d;
            if (this.columns == MlConstrants.MLINDEX_BYTES_NUM_2) {
                a = this.get(0, 0);
                b = this.get(0, 1);
                c = this.get(1, 0);
                d = this.get(1, 1);

                return a * d - b * c;
            } else if (this.columns == MlConstrants.MLINDEX_BYTES_NUM_3) {
                MlMatrixSelectionView subMatrix0;
                MlMatrixSelectionView subMatrix1;
                MlMatrixSelectionView subMatrix2;

                subMatrix0 = this.selectionView(new int[]{1, MlConstrants.MLINDEX_BYTES_NUM_2}, new int[]{1, MlConstrants.MLINDEX_BYTES_NUM_2});
                subMatrix1 = this.selectionView(new int[]{1, MlConstrants.MLINDEX_BYTES_NUM_2}, new int[]{0, MlConstrants.MLINDEX_BYTES_NUM_2});
                subMatrix2 = this.selectionView(new int[]{1, MlConstrants.MLINDEX_BYTES_NUM_2}, new int[]{0, 1});
                a = this.get(0, 0);
                b = this.get(0, 1);
                c = this.get(0, MlConstrants.MLINDEX_BYTES_NUM_2);

                return a * subMatrix0.det() - b * subMatrix1.det() + c * subMatrix2.det();
            } else {
                // general purpose determinant using the LU decomposition
                return new MlMatrixDclu(this).determinant();
            }
        } else {
            throw new Error("Determinant can only be calculated for a square matrix.");
        }
    }

    public void checkDimensions(Matrix matrix, Matrix otherMatrix) {
        if (matrix.rows != otherMatrix.rows || matrix.columns != otherMatrix.columns) {
            throw new RangeError("Matrices dimensions must be equal");
        }
    }

    public int compareNumbers(int a, int b) {
        return a - b;
    }

    public static Matrix inverse(Matrix matrix) {
        return MlConstrants.mlMatrixDecompositions.inverse(matrix);
    }

    public static Matrix inv(Matrix matrix) {
        return MlConstrants.mlMatrixDecompositions.inverse(matrix);
    }

    public Matrix inverse() {
        return MlConstrants.mlMatrixDecompositions.inverse(this);
    }

    public Matrix inv() {
        return inverse();
    }

    public Matrix solve(Matrix other) {
        return MlConstrants.mlMatrixDecompositions.solve(this, other);
    }

    public Matrix add(Object value) {
        if (value instanceof Double) {
            return this.addS((double) value);
        }
        return this.addM((Matrix) value);

    }

    public Matrix addS(double value) {
        for (int i = 0; i < this.rows; i++) {
            for (int j = 0; j < this.columns; j++) {
                this.set(i, j, this.get(i, j) + value);
            }
        }
        return this;
    }

    public Matrix addM(Matrix matrix) {
        Matrix matrix1 = Matrix.checkMatrix(matrix);
        checkDimensions(this, matrix1);
        for (int i = 0; i < this.rows; i++) {
            for (int j = 0; j < this.columns; j++) {
                this.set(i, j, this.get(i, j) + matrix1.get(i, j));
            }
        }
        return this;
    }

    public static Matrix add(Matrix matrix, Object value) {
        Matrix newMatrix = new Matrix(matrix, null);
        return newMatrix.add((Matrix) value, null);
    }

    public Matrix sub(Object value) {
        if (value instanceof Double) {
            return this.subS((double) value);
        }
        return this.subM((Matrix) value);

    }

    public Matrix subS(double value) {
        for (int i = 0; i < this.rows; i++) {
            for (int j = 0; j < this.columns; j++) {
                this.set(i, j, this.get(i, j) - value);
            }
        }
        return this;
    }

    public Matrix subM(Matrix matrix) {
        Matrix matrix1 = Matrix.checkMatrix(matrix);
        this.checkDimensions(this, matrix);
        for (int i = 0; i < this.rows; i++) {
            for (int j = 0; j < this.columns; j++) {
                this.set(i, j, this.get(i, j) - matrix.get(i, j));
            }
        }
        return this;
    }

    public static Matrix sub(Matrix matrix, Object value) {
        Matrix newMatrix = new Matrix(matrix, null);
        return newMatrix.sub(value);
    }

    public Matrix mul(Object value) {
        if (value instanceof Double) {
            return this.mulS((double) value);
        }
        return this.mulM((Matrix) value);
    }

    public Matrix mulS(double value) {
        for (int i = 0; i < this.rows; i++) {
            for (int j = 0; j < this.columns; j++) {
                this.set(i, j, this.get(i, j) * value);
            }
        }
        return this;
    }

    public Matrix mulM(Matrix matrix) {
        Matrix matrix1 = Matrix.checkMatrix(matrix);
        checkDimensions(this, matrix1);

        for (int i = 0; i < this.rows; i++) {
            for (int j = 0; j < this.columns; j++) {
                this.set(i, j, this.get(i, j) * matrix.get(i, j));
            }
        }
        return this;
    }


    public Matrix mul(Matrix matrix, Object value) {
        Matrix newMatrix = new Matrix(matrix, null);
        return newMatrix.mul(value);
    }


    public Matrix div(Object value) {
        if (value instanceof Double) {
            return this.divS((double) value);
        }
        return this.divM((Matrix) value);
    }

    public Matrix divS(double value) {

        for (int i = 0; i < this.rows; i++) {
            for (int j = 0; j < this.columns; j++) {
                this.set(i, j, this.get(i, j) / value);
            }
        }
        return this;
    }

    public Matrix divM(Matrix matrix) {
        Matrix matrix1 = Matrix.checkMatrix(matrix);
        checkDimensions(this, matrix);
        for (int i = 0; i < this.rows; i++) {
            for (int j = 0; j < this.columns; j++) {
                this.set(i, j, this.get(i, j) / matrix1.get(i, j));
            }
        }
        return this;
    }


    public Matrix div(Matrix matrix, Object value) {
        Matrix newMatrix = new Matrix(matrix, null);
        return newMatrix.div(value);
    }

    public Matrix removeRow(int index) {
        MlConstrants.mlMatrixUtil.checkRowIndex(this, index, false);
        if (this.rows == 1) {
            throw new RangeError("A matrix cannot have less than one row");
        }
        this.rows -= 1;
        return this;
    }

    public Matrix addRow(int index, double[] array) {
        MlConstrants.mlMatrixUtil.checkRowIndex(this, index, true);
        MlConstrants.mlMatrixUtil.checkRowVector(this, array);
        this.rows += 1;
        return this;
    }

    public Matrix removeColumn(int index) {
        MlConstrants.mlMatrixUtil.checkColumnIndex(this, index, false);
        if (this.columns == 1) {
            throw new RangeError("A matrix cannot have less than one column");
        }
        this.columns -= 1;
        return this;
    }

    public Matrix addColumn(int index, double[] array) {
        double[] newArray = array;
        MlConstrants.mlMatrixUtil.checkColumnIndex(this, index, true);
        newArray = MlConstrants.mlMatrixUtil.checkColumnVector(this, newArray);
        this.columns += 1;
        return this;
    }
}

class MlArrayUtilsArrayUtilsCls {
    public static double[][] coordArrayToPoints(double[] array, int dimensions) {
        if (array.length % dimensions != 0) {
            throw new RangeError("Dimensions number must be accordance with the size of the array");
        }
        int length = array.length / dimensions;
        double[][] pointsArr = new double[length][];

        int k = 0;
        int i = 0;
        for (i = 0; i < array.length; i += dimensions) {
            double[] point = new double[dimensions];
            Arrays.fill(point, 0);
            for (int j = 0; j < dimensions; ++j) {
                point[j] = array[i + j];
            }
            pointsArr[k] = point;
            k += 1;
            i += dimensions;
        }
        return pointsArr;
    }

    public static double[][] coordArrayToCoordMatrix(double[] array, int dimensions) {
        if (array.length % dimensions != 0) {
            throw new RangeError("Dimensions number must be accordance with the size of the array.");
        }
        double[][] coordinatesArray = new double[dimensions][];
        int points = array.length / dimensions;
        for (int i = 0; i < coordinatesArray.length; i++) {
            double[] arr = new double[points];
            Arrays.fill(arr, 0);
            coordinatesArray[i] = arr;
        }
        for (int i = 0; i < array.length; i += dimensions) {
            for (int j = 0; j < dimensions; ++j) {
                int currentPoint = (int) Math.floor(i / dimensions);
                coordinatesArray[j][currentPoint] = array[i + j];
            }
            i += dimensions;
        }
        return coordinatesArray;
    }

    public static double[] coordMatrixToCoordArray(double[][] coordMatrix) {

        double[] coodinatesArray = new double[coordMatrix.length * coordMatrix[0].length];
        Arrays.fill(coodinatesArray, 0);
        int k = 0;
        for (int i = 0; i < coordMatrix[0].length; ++i) {
            for (int j = 0; j < coordMatrix.length; ++j) {
                coodinatesArray[k] = coordMatrix[j][i];
                k += 1;
            }
        }
        return coodinatesArray;
    }

    public static double[][] transpose(double[][] matrix) {
        double[][] resultMatrix = new double[matrix[0].length][];
        for (int i = 0; i < resultMatrix.length; ++i) {
            double[] arr = new double[matrix.length];
            Arrays.fill(arr, 0);
            resultMatrix[i] = arr;
        }
        for (int i = 0; i < matrix.length; ++i) {
            for (int j = 0; j < matrix[i].length; ++j) {
                resultMatrix[j][i] = matrix[i][j];
            }
        }
        return resultMatrix;
    }

    public static double[] pointsToCoordArray(double[][] points) {
        double[] coodinatesArray = new double[points.length * points[0].length];
        int k = 0;
        for (int i = 0; i < points.length; ++i) {
            for (int j = 0; j < points[0].length; ++j) {
                coodinatesArray[k] = points[i][j];
                k += 1;
            }
        }
        return coodinatesArray;
    }

    public static double[] applyDotProduct(double[] firstVector, double[] secondVector) {
        double[] largestVector;
        double[] smallestVector;
        if (firstVector.length <= secondVector.length) {
            smallestVector = firstVector;
            largestVector = secondVector;
        } else {
            smallestVector = secondVector;
            largestVector = firstVector;
        }

        int difference = largestVector.length - smallestVector.length + 1;
        double[] dotProductApplied = new double[difference];
        Arrays.fill(dotProductApplied, 0.0);
        for (int i = 0; i < difference; ++i) {
            double sum = 0;
            for (int j = 0; j < smallestVector.length; ++j) {
                sum += smallestVector[j] * largestVector[i + j];
            }
            dotProductApplied[i] = sum;
        }
        return dotProductApplied;
    }

    public static Object scale(double[] input, MlOptions options) {
        double[] y;
        if (options.inPlace) {
            y = input;
        } else {
            y = new double[input.length];
            Arrays.fill(y, 0.0);
        }
        return y;
    }
}

// ml-matrix src/views/base
class MlMatrixBaseView extends Matrix {
    public Matrix matrix;

    MlMatrixBaseView(Matrix matrix, int rows, int columns) {
        super(matrix, null);
        this.matrix = matrix;
        this.rows = rows;
        this.columns = columns;

    }
}

// ml-matrix src/views/column.js
class MlMatrixColumnView extends MlMatrixBaseView {
    public int column;

    MlMatrixColumnView(Matrix matrix, int column) {
        super(matrix, matrix.rows, 1);
        this.column = column;
    }

    public MlMatrixColumnView set(int rowIndex, int columnIndex, double value) {
        this.matrix.set(rowIndex, this.column, value);
        return this;
    }

    public double get(int rowIndex) {
        return this.matrix.get(rowIndex, this.column);
    }
}

// ml-matrix src/views/flipColumn.js
class MlMatrixFlipColumnView extends MlMatrixBaseView {

    MlMatrixFlipColumnView(Matrix matrix) {
        super(matrix, matrix.rows, matrix.columns);
    }

    public MlMatrixFlipColumnView set(int rowIndex, int columnIndex, double value) {
        this.matrix.set(rowIndex, this.columns - columnIndex - 1, value);
        return this;
    }

    public double get(int rowIndex, int columnIndex) {
        return this.matrix.get(rowIndex, this.columns - columnIndex - 1);
    }
}

// ml-matrix src/views/flipRow.js
class MlMatrixFlipRowView extends MlMatrixBaseView {

    MlMatrixFlipRowView(Matrix matrix) {
        super(matrix, 1, matrix.columns);
    }

    public MlMatrixFlipRowView set(int rowIndex, int columnIndex, double value) {
        this.matrix.set(this.rows - rowIndex - 1, columnIndex, value);
        return this;
    }

    public double get(int rowIndex, int columnIndex) {
        return this.matrix.get(this.rows - rowIndex - 1, columnIndex);
    }
}

// ml-matrix src/views/row.js
class MlMatrixRowView extends MlMatrixBaseView {

    public int row;

    MlMatrixRowView(Matrix matrix, int row) {
        super(matrix, 1, matrix.columns);
        this.row = row;
    }

    public MlMatrixRowView set(int rowIndex, int columnIndex, double value) {
        this.matrix.set(this.row, columnIndex, value);
        return this;
    }

    public double get(int rowIndex, int columnIndex) {
        return this.matrix.get(this.row, columnIndex);
    }
}

// ml-matrix src/views/selection.js
class MlMatrixSelectionView extends MlMatrixBaseView {
    public int[] rowIndices;
    public int[] columnIndices;

    MlMatrixSelectionView(Matrix matrix, int[] rowIndices, int[] columnIndices) {
        super(matrix, 0, 0);
        Tuple indices = MlConstrants.mlMatrixUtil.checkIndices(matrix, rowIndices, columnIndices);
        this.rowIndices = (int[]) indices.first;
        this.columnIndices = (int[]) indices.second;
    }

    public MlMatrixSelectionView set(int rowIndex, int columnIndex, double value) {
        this.matrix.set(this.rowIndices[rowIndex], this.columnIndices[columnIndex], value);
        return this;
    }

    public double get(int rowIndex, int columnIndex) {
        return this.matrix.get(this.rowIndices[rowIndex], this.columnIndices[columnIndex]);
    }

}

// ml-matrix src/views/sub.js
class MlMatrixSubView extends MlMatrixBaseView {

    public int startRow;
    public int startColumn;


    MlMatrixSubView(Matrix matrix, int startRow, int endRow, int startColumn, int endColumn) {
        super(matrix, endRow - startRow + 1, endColumn - startColumn + 1);
        MlConstrants.mlMatrixUtil.checkRange(matrix, startRow, endRow, startColumn, endColumn);
        this.startRow = startRow;
        this.startColumn = startColumn;
    }

    public MlMatrixSubView set(int rowIndex, int columnIndex, double value) {
        this.matrix.set(this.startRow + rowIndex, this.startColumn + columnIndex, value);
        return this;
    }

    public double get(int rowIndex, int columnIndex) {
        return this.matrix.get(this.startRow + rowIndex, this.startColumn + columnIndex);
    }
}
// ml-matrix src/views/transpose.js
class MlMatrixTransposeView extends MlMatrixBaseView {


    MlMatrixTransposeView(Matrix matrix) {
        super(matrix, matrix.columns, matrix.rows);
    }

    public MlMatrixTransposeView set(int rowIndex, int columnIndex, double value) {
        this.matrix.set(columnIndex, rowIndex, value);
        return this;
    }

    public double get(int rowIndex, int columnIndex) {
        return this.matrix.get(columnIndex, rowIndex);
    }
}

// ml-matrix src/dc/cholesky.js
class MlMatrixDcCholesky {
    public Matrix l;

    MlMatrixDcCholesky(Matrix value) {
        value = Matrix.checkMatrix(value);

        if (!value.isSymmetric()) {
            throw new Error("Matrix is not symmetric");
        }

        Matrix a = value;
        int dimension = a.rows;
        Matrix l = new Matrix(dimension, dimension);
        boolean positiveDefinite = true;

        for (int j = 0; j < dimension; j++) {
            double[] lRowj = l.getRow(j);
            double d = 0.0;
            for (int k = 0; k < j; k++) {
                double[] lRowk = l.getRow(k);
                double s = 0.0;
                for (int i = 0; i < k; i++) {
                    s += lRowk[i] * lRowj[i];
                }
                s = (a.get(j, k) - s) / l.get(k, k);
                lRowj[k] = s;
                d = d + s * s;
            }
            d = a.get(j, j) - d;
            if (d > 0) {
                positiveDefinite = true;
            } else {
                positiveDefinite = false;
            }
            l.set(j, j, Math.sqrt(Math.max(d, 0)));
            for (int k = j + 1; k < dimension; k++) {
                l.set(j, k, 0);
            }
        }
        if (!positiveDefinite) {
            throw new Error("Matrix is not positive definite");
        }
        this.l = l;
    }

    public Object lowerTriangularMatrix() {
        return this.l;
    }

    public Object solve(Object value) {
        value = Matrix.checkMatrix(value);
        Matrix l = this.l;
        int dimension = l.rows;

        if (((Matrix) value).rows != dimension) {
            throw new Error("Matrix dimensions do not match");
        }

        int count = ((Matrix) value).columns;
        Matrix b = ((Matrix) value).clone();

        for (int k = 0; k < dimension; k++) {
            for (int j = 0; j < count; j++) {
                for (int i = 0; i < k; i++) {
                    b.set(k, j, b.get(k, j) - b.get(i, j) * l.get(k, i));
                }
                b.set(k, j, b.get(k, j) / l.get(k, k));
            }
        }
        for (int k = dimension - 1; k >= 0; k--) {
            for (int j = 0; j < count; j++) {
                for (int i = k + 1; i < dimension; i++) {
                    b.set(k, j, b.get(k, j) - b.get(i, j) * l.get(i, k));
                }
                b.set(k, j, b.get(k, j) / l.get(k, k));
            }
        }
        return b;
    }
}

// ml-matrix src/dc/evd.js
class MlMatrixDcevd {
    public int n = 0;
    public double[] e;
    public double[] d;
    public double[][] v;
    public Matrix m1;

    MlMatrixDcevd(Matrix matrix, MlOptions options) {
        matrix = Matrix.checkMatrix(matrix);
        if (!matrix.isSquare()) {
            throw new Error("Matrix is not a square matrix");
        }
        int n = matrix.columns;
        double[][] v = MlConstrants.mlMatrixDcuTil.getFilled2dArray(n, n, 0);
        double[] d = new double[n];
        Arrays.fill(d, 0.0);
        double[] e = new double[n];
        Arrays.fill(e, 0.0);

        Matrix value = matrix;

        boolean isSymmetric = false;
        if (options.defaultOptions.get("assumeSymmetric")) {
            isSymmetric = true;
        } else {
            isSymmetric = matrix.isSymmetric();
        }
        if (isSymmetric) {
            for (int i = 0; i < n; i++) {
                for (int j = 0; j < n; j++) {
                    v[i][j] = value.get(i, j);
                }
            }
            this.tred2(n, e, d, v);
            this.tql2(n, e, d, v);
        } else {
            double[][] h = MlConstrants.mlMatrixDcuTil.getFilled2dArray(n, n, 0);
            double[] ort = new double[n];
            Arrays.fill(ort, 0.0);
            for (int j = 0; j < n; j++) {
                for (int i = 0; i < n; i++) {
                    h[i][j] = value.get(i, j);
                }
            }
            this.orthes(n, h, ort, v);
            this.hqr2(n, e, d, v, h);
        }
        this.n = n;
        this.e = e;
        this.d = d;
        this.v = v;
    }

    public double[] realEigenvalues() {
        return this.d;
    }

    public double[] imaginaryEigenvalues() {
        return this.e;
    }

    public Matrix eigenvectorMatrix() {
        if (!Matrix.isMatrix(this.v)) {
            this.m1 = new Matrix(this.v, null);
        }
        return this.m1;
    }

    public Matrix diagonalMatrix() {
        int n = this.n;
        double[] e = this.e;
        double[] d = this.d;
        Matrix x = new Matrix(n, n);

        for (int i = 0; i < n; i++) {
            for (int j = 0; j < n; j++) {
                x.set(i, j, 0);
            }
            x.set(i, i, d[i]);
            if (e[i] > 0) {
                x.set(i, i + 1, e[i]);
            } else if (e[i] < 0) {
                x.set(i, i - 1, e[i]);
            }
        }
        return x;
    }

    public void tred2(int n, double[] e, double[] d, double[][] v) {
        double f;
        double g;
        double h;
        double hh;
        double scale;
        double[] ei = e;
        double[] di = d;
        double[][] vi = v;

        for (int j = 0; j < n; j++) {
            di[j] = vi[n - 1][j];
        }

        for (int i = n - 1; i > 0; i--) {
            scale = 0;
            h = 0;
            for (int k = 0; k < i; k++) {
                scale = scale + Math.abs(di[k]);
            }

            if (scale == 0) {
                ei[i] = di[i - 1];
                for (int j = 0; j < i; j++) {
                    di[j] = vi[i - 1][j];
                    vi[i][j] = 0;
                }
            } else {
                for (int k = 0; k < i; k++) {
                    di[k] /= scale;
                    h += di[k] * di[k];
                }

                f = di[i - 1];
                g = Math.sqrt(h);
                if (f > 0) {
                    g = -g;
                }

                ei[i] = scale * g;
                h = h - f * g;
                di[i - 1] = f - g;
                for (int j = 0; j < i; j++) {
                    ei[j] = 0;
                }

                for (int j = 0; j < i; j++) {
                    f = di[j];
                    vi[j][i] = f;
                    g = ei[j] + vi[j][j] * f;
                    for (int k = j + 1; k <= i - 1; k++) {
                        g += vi[k][j] * di[k];
                        ei[k] += vi[k][j] * f;
                    }
                    ei[j] = g;
                }

                f = 0;
                for (int j = 0; j < i; j++) {
                    ei[j] /= h;
                    f += ei[j] * di[j];
                }

                hh = f / (h + h);
                for (int j = 0; j < i; j++) {
                    ei[j] -= hh * di[j];
                }

                for (int j = 0; j < i; j++) {
                    f = di[j];
                    g = ei[j];
                    for (int k = j; k <= i - 1; k++) {
                        vi[k][j] -= f * ei[k] + g * di[k];
                    }
                    di[j] = vi[i - 1][j];
                    vi[i][j] = 0;
                }
            }
            di[i] = h;
        }

        for (int i = 0; i < n - 1; i++) {
            vi[n - 1][i] = vi[i][i];
            vi[i][i] = 1;
            h = di[i + 1];
            if (h != 0) {
                for (int k = 0; k <= i; k++) {
                    di[k] = vi[k][i + 1] / h;
                }
                for (int j = 0; j <= i; j++) {
                    g = 0;
                    for (int k = 0; k <= i; k++) {
                        g += vi[k][i + 1] * vi[k][j];
                    }
                    for (int k = 0; k <= i; k++) {
                        vi[k][j] -= g * di[k];
                    }
                }
            }

            for (int k = 0; k <= i; k++) {
                vi[k][i + 1] = 0;
            }
        }

        for (int j = 0; j < n; j++) {
            di[j] = vi[n - 1][j];
            vi[n - 1][j] = 0;
        }

        vi[n - 1][n - 1] = 1;
        ei[0] = 0;
    }

    public void tql2(int n, double[] e, double[] d, double[][] v) {
        double g;
        double h;
        int k;
        int m;
        double p;
        double r;
        double dl1;
        double c;
        double c2;
        double c3;
        double el1;
        double s;
        double s2;
        double iter;
        double[] ei = e;
        double[] di = d;
        double[][] vi = v;

        for (int i = 1; i < n; i++) {
            ei[i - 1] = ei[i];
        }

        ei[n - 1] = 0;

        double f = 0.0;
        double tst1 = 0.0;
        double eps = Math.pow(MlConstrants.MLINDEX_BYTES_NUM_20, MlConstrants.MLINDEX_BYTES_NUM_0520);

        for (int l = 0; l < n; l++) {
            tst1 = Math.max(tst1, Math.abs(di[l]) + Math.abs(ei[l]));
            m = l;
            while (m < n) {
                if (Math.abs(ei[m]) <= eps * tst1) {
                    break;
                }
                m += 1;
            }

            if (m > l) {
                iter = 0;
                do {
                    iter = iter + 1;

                    g = di[l];
                    p = (di[l + 1] - g) / (MlConstrants.MLINDEX_BYTES_NUM_2 * ei[l]);
                    r = MlConstrants.mlMatrixDcuTil.hypotenuse(p, 1);
                    if (p < 0) {
                        r = -r;
                    }

                    di[l] = ei[l] / (p + r);
                    di[l + 1] = ei[l] * (p + r);
                    dl1 = di[l + 1];
                    h = g - di[l];
                    for (int i = l + MlConstrants.MLINDEX_BYTES_NUM_2; i < n; i++) {
                        di[i] -= h;
                    }

                    f = f + h;
                    p = di[m];
                    c = 1;
                    c2 = c;
                    c3 = c;
                    el1 = ei[l + 1];
                    s = 0;
                    s2 = 0;
                    for (int i = m - 1; i >= l; i--) {
                        c3 = c2;
                        c2 = c;
                        s2 = s;
                        g = c * ei[i];
                        h = c * p;
                        r = MlConstrants.mlMatrixDcuTil.hypotenuse(p, ei[i]);
                        ei[i + 1] = s * r;
                        s = ei[i] / r;
                        c = p / r;
                        p = c * di[i] - s * g;
                        di[i + 1] = h + s * (c * g + s * di[i]);
                        for (k = 0; k < n; k++) {
                            h = vi[k][i + 1];
                            vi[k][i + 1] = s * vi[k][i] + c * h;
                            vi[k][i] = c * vi[k][i] - s * h;
                        }
                    }

                    p = (-s * s2 * c3 * el1 * ei[l]) / dl1;
                    ei[l] = s * p;
                    di[l] = c * p;
                } while (Math.abs(ei[l]) > eps * tst1);
            }
            di[l] = di[l] + f;
            ei[l] = 0;
        }

        for (int i = 0; i < n - 1; i++) {
            k = i;
            p = di[i];
            for (int j = i + 1; j < n; j++) {
                if (di[j] < p) {
                    k = j;
                    p = di[j];
                }
            }

            if (k != i) {
                di[k] = di[i];
                di[i] = p;
                for (int j = 0; j < n; j++) {
                    p = vi[j][i];
                    vi[j][i] = vi[j][k];
                    vi[j][k] = p;
                }
            }
        }
    }

    public void orthes(int n, double[][] h, double[] ort, double[][] v) {
        int low = 0;
        int high = n - 1;
        double f;
        double g;
        double hl;
        double scale;
        double[][] hi = h;
        double[] orti = ort;
        double[][] vi = v;

        for (int m = low + 1; m <= high - 1; m++) {
            scale = 0;
            for (int i = m; i <= high; i++) {
                scale = scale + Math.abs(hi[i][m - 1]);
            }

            if (scale != 0) {
                hl = 0;
                for (int i = high; i >= m; i--) {
                    orti[i] = hi[i][m - 1] / scale;
                    hl += orti[i] * orti[i];
                }

                g = Math.sqrt(hl);
                if (orti[m] > 0) {
                    g = -g;
                }

                hl = hl - orti[m] * g;
                orti[m] = orti[m] - g;

                for (int j = m; j < n; j++) {
                    f = 0;
                    for (int i = high; i >= m; i--) {
                        f += orti[i] * hi[i][j];
                    }

                    f = f / hl;
                    for (int i = m; i <= high; i++) {
                        hi[i][j] -= f * orti[i];
                    }
                }

                for (int i = 0; i <= high; i++) {
                    f = 0;
                    for (int j = high; j >= m; j--) {
                        f += orti[j] * hi[i][j];
                    }

                    f = f / hl;
                    for (int j = m; j <= high; j++) {
                        hi[i][j] -= f * orti[j];
                    }
                }

                orti[m] = scale * orti[m];
                hi[m][m - 1] = scale * g;
            }
        }

        for (int i = 0; i < n; i++) {
            for (int j = 0; j < n; j++) {
                vi[i][j] = i == j ? 1 : 0;
            }
        }

        for (int m = high - 1; m >= low + 1; m--) {
            if (hi[m][m - 1] != 0) {
                for (int i = m + 1; i <= high; i++) {
                    orti[i] = hi[i][m - 1];
                }

                for (int j = m; j <= high; j++) {
                    g = 0.0;
                    for (int i = m; i <= high; i++) {
                        g += orti[i] * vi[i][j];
                    }

                    g = g / orti[m] / hi[m][m - 1];
                    for (int i = m; i <= high; i++) {
                        vi[i][j] += g * orti[i];
                    }
                }
            }
        }
    }

    public void hqr2(int nn, double[] e, double[] d, double[][] v, double[][] h) {
        int n = nn - 1;
        int low = 0;
        int high = nn - 1;
        double eps = Math.pow(MlConstrants.MLINDEX_BYTES_NUM_20, MlConstrants.MLINDEX_BYTES_NUM_0520);
        double exshift = 0.0;
        double norm = 0.0;
        double p = 0.0;
        double q = 0.0;
        double r = 0.0;
        double s = 0.0;
        double z = 0.0;
        double iter = 0.0;
        int l;
        int m;
        double t;
        double w;
        double x;
        double y;
        double ra;
        double sa;
        double vr;
        double vi;
        boolean notlast;
        double[] cdivres;

        int nni = nn;
        double[] ei = e;
        double[] di = d;
        double[][] vd = v;
        double[][] hi = h;

        for (int i = 0; i < nni; i++) {
            if (i < low || i > high) {
                di[i] = hi[i][i];
                ei[i] = 0;
            }

            for (int j = Math.max(i - 1, 0); j < nni; j++) {
                norm = norm + Math.abs(hi[i][j]);
            }
        }

        while (n >= low) {
            l = n;
            while (l > low) {
                s = Math.abs(hi[l - 1][l - 1]) + Math.abs(hi[l][l]);
                if (s == 0) {
                    s = norm;
                }
                if (Math.abs(hi[l][l - 1]) < eps * s) {
                    break;
                }
                l -= 1;
            }

            if (l == n) {
                hi[n][n] = hi[n][n] + exshift;
                di[n] = hi[n][n];
                ei[n] = 0;
                n -= 1;
                iter = 0;
            } else if (l == n - 1) {
                w = hi[n][n - 1] * hi[n - 1][n];
                p = (hi[n - 1][n - 1] - hi[n][n]) / MlConstrants.MLINDEX_BYTES_NUM_2;
                q = p * p + w;
                z = Math.sqrt(Math.abs(q));
                hi[n][n] = hi[n][n] + exshift;
                hi[n - 1][n - 1] = hi[n - 1][n - 1] + exshift;
                x = hi[n][n];

                if (q >= 0) {
                    z = p >= 0 ? p + z : p - z;
                    di[n - 1] = x + z;
                    di[n] = di[n - 1];
                    if (z != 0) {
                        di[n] = x - w / z;
                    }
                    ei[n - 1] = 0;
                    ei[n] = 0;
                    x = hi[n][n - 1];
                    s = Math.abs(x) + Math.abs(z);
                    p = x / s;
                    q = z / s;
                    r = Math.sqrt(p * p + q * q);
                    p = p / r;
                    q = q / r;

                    for (int j = n - 1; j < nni; j++) {
                        z = hi[n - 1][j];
                        hi[n - 1][j] = q * z + p * hi[n][j];
                        hi[n][j] = q * hi[n][j] - p * z;
                    }

                    for (int i = 0; i <= n; i++) {
                        z = hi[i][n - 1];
                        hi[i][n - 1] = q * z + p * hi[i][n];
                        hi[i][n] = q * hi[i][n] - p * z;
                    }

                    for (int i = low; i <= high; i++) {
                        z = vd[i][n - 1];
                        vd[i][n - 1] = q * z + p * vd[i][n];
                        vd[i][n] = q * vd[i][n] - p * z;
                    }
                } else {
                    di[n - 1] = x + p;
                    di[n] = x + p;
                    ei[n - 1] = z;
                    ei[n] = -z;
                }

                n = n - MlConstrants.MLINDEX_BYTES_NUM_2;
                iter = 0;
            } else {
                x = hi[n][n];
                y = 0;
                w = 0;
                if (l < n) {
                    y = hi[n - 1][n - 1];
                    w = hi[n][n - 1] * hi[n - 1][n];
                }

                if (iter == MlConstrants.MLINDEX_BYTES_NUM_10) {
                    exshift += x;
                    for (int i = low; i <= n; i++) {
                        hi[i][i] -= x;
                    }
                    s = Math.abs(hi[n][n - 1]) + Math.abs(hi[n - 1][n - MlConstrants.MLINDEX_BYTES_NUM_2]);
                    y = MlConstrants.MLINDEX_BYTES_NUM_075 * s;
                    x = y;
                    w = MlConstrants.MLINDEX_BYTES_NUM_04375 * s * s;
                }

                if (iter == MlConstrants.MLINDEX_BYTES_NUM_30) {
                    s = (y - x) / MlConstrants.MLINDEX_BYTES_NUM_2;
                    s = s * s + w;
                    if (s > 0) {
                        s = Math.sqrt(s);
                        if (y < x) {
                            s = -s;
                        }
                        s = x - w / ((y - x) / MlConstrants.MLINDEX_BYTES_NUM_2 + s);
                        for (int i = low; i <= n; i++) {
                            hi[i][i] -= s;
                        }
                        exshift += s;
                        w = MlConstrants.MLINDEX_BYTES_NUM_0964;
                        y = w;
                        x = y;
                    }
                }

                iter = iter + 1;

                m = n - MlConstrants.MLINDEX_BYTES_NUM_2;
                while (m >= l) {
                    z = hi[m][m];
                    r = x - z;
                    s = y - z;
                    p = (r * s - w) / hi[m + 1][m] + hi[m][m + 1];
                    q = hi[m + 1][m + 1] - z - r - s;
                    r = hi[m + MlConstrants.MLINDEX_BYTES_NUM_2][m + 1];
                    s = Math.abs(p) + Math.abs(q) + Math.abs(r);
                    p = p / s;
                    q = q / s;
                    r = r / s;
                    if (m == l) {
                        break;
                    }
                    if (
                            Math.abs(hi[m][m - 1]) * (Math.abs(q) + Math.abs(r)) <
                                    eps * (Math.abs(p) * (Math.abs(hi[m - 1][m - 1]) + Math.abs(z) + Math.abs(hi[m + 1][m + 1])))
                    ) {
                        break;
                    }
                    m -= 1;
                }

                for (int i = m + MlConstrants.MLINDEX_BYTES_NUM_2; i <= n; i++) {
                    hi[i][i - MlConstrants.MLINDEX_BYTES_NUM_2] = 0;
                    if (i > m + MlConstrants.MLINDEX_BYTES_NUM_2) {
                        hi[i][i - MlConstrants.MLINDEX_BYTES_NUM_3] = 0;
                    }
                }
                for (int k = m; k <= n - 1; k++) {
                    notlast = k != n - 1;
                    if (k != m) {
                        p = hi[k][k - 1];
                        q = hi[k + 1][k - 1];
                        r = notlast ? hi[k + MlConstrants.MLINDEX_BYTES_NUM_2][k - 1] : 0.0;
                        x = Math.abs(p) + Math.abs(q) + Math.abs(r);
                        if (x != 0) {
                            p = p / x;
                            q = q / x;
                            r = r / x;
                        }
                    }

                    if (x == 0) {
                        break;
                    }

                    s = Math.sqrt(p * p + q * q + r * r);
                    if (p < 0) {
                        s = -s;
                    }

                    if (s != 0) {
                        if (k != m) {
                            hi[k][k - 1] = -s * x;
                        } else if (l != m) {
                            hi[k][k - 1] = -hi[k][k - 1];
                        }

                        p = p + s;
                        x = p / s;
                        y = q / s;
                        z = r / s;
                        q = q / p;
                        r = r / p;

                        for (int j = k; j < nni; j++) {
                            p = hi[k][j] + q * hi[k + 1][j];
                            if (notlast) {
                                p = p + r * hi[k + MlConstrants.MLINDEX_BYTES_NUM_2][j];
                                hi[k + MlConstrants.MLINDEX_BYTES_NUM_2][j] = hi[k + MlConstrants.MLINDEX_BYTES_NUM_2][j] - p * z;
                            }

                            hi[k][j] = hi[k][j] - p * x;
                            hi[k + 1][j] = hi[k + 1][j] - p * y;
                        }

                        for (int i = 0; i <= Math.min(n, k + MlConstrants.MLINDEX_BYTES_NUM_3); i++) {
                            p = x * hi[i][k] + y * hi[i][k + 1];
                            if (notlast) {
                                p = p + z * hi[i][k + MlConstrants.MLINDEX_BYTES_NUM_2];
                                hi[i][k + MlConstrants.MLINDEX_BYTES_NUM_2] = hi[i][k + MlConstrants.MLINDEX_BYTES_NUM_2] - p * r;
                            }

                            hi[i][k] = hi[i][k] - p;
                            hi[i][k + 1] = hi[i][k + 1] - p * q;
                        }

                        for (int i = low; i <= high; i++) {
                            p = x * vd[i][k] + y * vd[i][k + 1];
                            if (notlast) {
                                p = p + z * vd[i][k + MlConstrants.MLINDEX_BYTES_NUM_2];
                                vd[i][k + MlConstrants.MLINDEX_BYTES_NUM_2] = vd[i][k + MlConstrants.MLINDEX_BYTES_NUM_2] - p * r;
                            }

                            vd[i][k] = vd[i][k] - p;
                            vd[i][k + 1] = vd[i][k + 1] - p * q;
                        }
                    }
                }
            }
        }

        if (norm == 0) {
            return;
        }

        for (n = nni - 1; n >= 0; n--) {
            p = di[n];
            q = ei[n];

            if (q == 0) {
                l = n;
                hi[n][n] = 1;
                for (int i = n - 1; i >= 0; i--) {
                    w = hi[i][i] - p;
                    r = 0.0;
                    for (int j = l; j <= n; j++) {
                        r = r + hi[i][j] * hi[j][n];
                    }

                    if (ei[i] < 0) {
                        z = w;
                        s = r;
                    } else {
                        l = i;
                        if (ei[i] == 0) {
                            if (w != 0) {
                                hi[i][n] = -r / w;
                            } else {
                                hi[i][n] = -r / (eps * norm);
                            }
                        } else {
                            x = hi[i][i + 1];
                            y = hi[i + 1][i];
                            q = (di[i] - p) * (di[i] - p) + ei[i] * ei[i];
                            t = x * s - (z * r) / q;
                            hi[i][n] = t;
                            if (Math.abs(x) > Math.abs(z)) {
                                hi[i + 1][n] = -r - (w * t) / x;
                            } else {
                                hi[i + 1][n] = -s - (y * t) / z;
                            }
                        }

                        t = Math.abs(hi[i][n]);
                        if (eps * t * t > 1) {
                            for (int j = i; j <= n; j++) {
                                hi[j][n] = hi[j][n] / t;
                            }
                        }
                    }
                }
            } else if (q < 0) {
                l = n - 1;

                if (Math.abs(hi[n][n - 1]) > Math.abs(hi[n - 1][n])) {
                    hi[n - 1][n - 1] = q / hi[n][n - 1];
                    hi[n - 1][n] = -(hi[n][n] - p) / hi[n][n - 1];
                } else {
                    cdivres = this.cdiv(0.0, -hi[n - 1][n], hi[n - 1][n - 1] - p, q);
                    hi[n - 1][n - 1] = cdivres[0];
                    hi[n - 1][n] = cdivres[1];
                }

                hi[n][n - 1] = 0;
                hi[n][n] = 1;
                for (int i = n - MlConstrants.MLINDEX_BYTES_NUM_2; i >= 0; i--) {
                    ra = 0;
                    sa = 0;
                    for (int j = l; j <= n; j++) {
                        ra = ra + hi[i][j] * hi[j][n - 1];
                        sa = sa + hi[i][j] * hi[j][n];
                    }

                    w = hi[i][i] - p;

                    if (ei[i] < 0) {
                        z = w;
                        r = ra;
                        s = sa;
                    } else {
                        l = i;
                        if (ei[i] == 0) {
                            cdivres = this.cdiv(-ra, -sa, w, q);
                            hi[i][n - 1] = cdivres[0];
                            hi[i][n] = cdivres[1];
                        } else {
                            x = hi[i][i + 1];
                            y = hi[i + 1][i];
                            vr = (di[i] - p) * (di[i] - p) + ei[i] * ei[i] - q * q;
                            vi = (di[i] - p) * MlConstrants.MLINDEX_BYTES_NUM_2 * q;
                            if (vr == 0 && vi == 0) {
                                vr = eps * norm * (Math.abs(w) + Math.abs(q) + Math.abs(x) + Math.abs(y) + Math.abs(z));
                            }
                            cdivres = this.cdiv(x * r - z * ra + q * sa, x * s - z * sa - q * ra, vr, vi);
                            hi[i][n - 1] = cdivres[0];
                            hi[i][n] = cdivres[1];
                            if (Math.abs(x) > Math.abs(z) + Math.abs(q)) {
                                hi[i + 1][n - 1] = (-ra - w * hi[i][n - 1] + q * hi[i][n]) / x;
                                hi[i + 1][n] = (-sa - w * hi[i][n] - q * hi[i][n - 1]) / x;
                            } else {
                                cdivres = this.cdiv(-r - y * hi[i][n - 1], -s - y * hi[i][n], z, q);
                                hi[i + 1][n - 1] = cdivres[0];
                                hi[i + 1][n] = cdivres[1];
                            }
                        }

                        t = Math.max(Math.abs(hi[i][n - 1]), Math.abs(hi[i][n]));
                        if (eps * t * t > 1) {
                            for (int j = i; j <= n; j++) {
                                hi[j][n - 1] = hi[j][n - 1] / t;
                                hi[j][n] = hi[j][n] / t;
                            }
                        }
                    }
                }
            }
        }

        for (int i = 0; i < nni; i++) {
            if (i < low || i > high) {
                for (int j = i; j < nni; j++) {
                    vd[i][j] = hi[i][j];
                }
            }
        }

        for (int j = nni - 1; j >= low; j--) {
            for (int i = low; i <= high; i++) {
                z = 0;
                for (int k = low; k <= Math.min(j, high); k++) {
                    z = z + vd[i][k] * hi[k][j];
                }
                vd[i][j] = z;
            }
        }
    }

    public double[] cdiv(double xr, double xi, double yr, double yi) {
        double r;
        double d;
        if (Math.abs(yr) > Math.abs(yi)) {
            r = yi / yr;
            d = yr + r * yi;
            return new double[]{(xr + r * xi) / d, (xi - r * xr) / d};
        } else {
            r = yr / yi;
            d = yi + r * yr;
            return new double[]{(r * xr + xi) / d, (r * xi - xr) / d};
        }
    }

}

// ml-matrix src/dc/qr.js
class MlMatrixDcqr {
    public Matrix qr;
    public double[] rdiag;

    MlMatrixDcqr(Matrix qr) {
        this.qr = qr;
    }


    public void qrDecomposition(double[][] value) {
        Matrix newValue = Matrix.checkMatrix(value);
        Matrix qr = newValue.clone();
        int m = newValue.rows;
        int n = newValue.columns;
        double[] rdiag = new double[n];
        Arrays.fill(rdiag, 0);
        double s;

        for (int k = 0; k < n; k++) {
            int nrm = 0;
            for (int i = k; i < m; i++) {
                nrm = MlConstrants.mlMatrixDcuTil.hypotenuse(nrm, (int) qr.get(i, k));
            }
            if (nrm != 0) {
                if (qr.get(k, k) < 0) {
                    nrm = -nrm;
                }
                for (int i = k; i < m; i++) {
                    qr.set(i, k, qr.get(i, k) / nrm);
                }
                qr.set(k, k, qr.get(k, k) + 1);
                for (int j = k + 1; j < n; j++) {
                    s = 0.0;
                    for (int i = k; i < m; i++) {
                        s += qr.get(i, k) * qr.get(i, j);
                    }
                    s = -s / qr.get(k, k);
                    for (int i = k; i < m; i++) {
                        qr.set(i, j, qr.get(i, j) + s * qr.get(i, k));
                    }
                }
            }
            rdiag[k] = -nrm;
        }
        this.qr = qr;
        this.rdiag = rdiag;
    }

    public Matrix solve(Object value) {
        Matrix newValue = Matrix.checkMatrix(value);
        Matrix qr = this.qr;
        int m = qr.rows;
        if ((newValue).rows != m) {
            throw new Error("Matrix row dimensions must agree");
        }
        if (!this.isFullRank()) {
            throw new Error("Matrix is rank deficient");
        }
        int count = newValue.columns;
        Matrix x = newValue.clone();
        int n = qr.columns;
        double s;
        for (int k = 0; k < n; k++) {
            for (int j = 0; j < count; j++) {
                s = 0;
                for (int i = k; i < m; i++) {
                    s += qr.get(i, k) * x.get(i, j);
                }
                s = -s / qr.get(k, k);
                for (int i = k; i < m; i++) {
                    x.set(i, j, x.get(i, j) + s * qr.get(i, k));
                }
            }
        }
        for (int k = n; k >= 0; k--) {
            for (int j = 0; j < count; j++) {
                x.set(k, j, x.get(k, j) / this.rdiag[k]);
            }
            for (int i = 0; i < k; i++) {
                for (int j = 0; j < count; j++) {
                    x.set(i, j, x.get(k, j) - x.get(k, j) * qr.get(i, k));
                }
            }
        }
        return x.subMatrix(0, n - 1, 0, count - 1);
    }

    public boolean isFullRank() {
        int columns = this.qr.columns;

        for (int i = 0; i < columns; i++) {
            if (this.rdiag[i] == 0) {
                return false;
            }
        }
        return true;
    }

    public Matrix upperTriangularMatrix() {
        Matrix qr = this.qr;
        int n = qr.columns;
        Matrix x = new Matrix(n, n);
        for (int i = 0; i < n; i++) {
            for (int j = 0; j < n; j++) {
                if (i < j) {
                    x.set(i, j, qr.get(i, j));
                } else if (i == j) {
                    x.set(i, j, this.rdiag[i]);
                } else {
                    x.set(i, j, 0);
                }
            }
        }
        return x;
    }

    public Matrix orthogonalMatrix() {
        Matrix qr = this.qr;
        int rows = qr.rows;
        int columns = qr.columns;
        Matrix x = new Matrix(rows, columns);
        double s;
        for (int k = columns - 1; k >= 0; k--) {
            for (int i = 0; i < rows; i++) {
                x.set(i, k, 0);
            }
            x.set(k, k, 1.0);
            for (int j = k; j < columns; j++) {
                if (qr.get(k, k) != 0) {
                    s = 0;
                    for (int i = k; i < rows; i++) {
                        s += qr.get(i, k) * x.get(i, j);
                    }
                    s = -s / qr.get(k, k);
                    for (int i = k; i < rows; i++) {
                        x.set(i, j, x.get(i, j) + s * qr.get(i, k));
                    }
                }
            }
        }
        return x;
    }
}

// ml-matric src/decompositions.js
class MlMatrixDecompositionsCls {

    public Matrix inverse(Matrix matrix) {
        matrix = Matrix.checkMatrix(matrix);
        return MlConstrants.mlMatrixDecompositions.solve(matrix, Matrix.eye(matrix.rows, null, null));
    }

    public Matrix solve(Matrix leftHandSide, Matrix rightHandSide) {
        leftHandSide = Matrix.checkMatrix(leftHandSide);
        rightHandSide = Matrix.checkMatrix(rightHandSide);
        if (leftHandSide.isSquare()) {
            return new MlMatrixDclu(leftHandSide).matrix.solve(rightHandSide);
        } else {
            return new MlMatrixDcqr(leftHandSide).solve(rightHandSide);
        }
    }
}

// feedforward-neural-networks utils.js
class FeedforwardNeuralNetworksUtilsCls {

    public Matrix sumRow(Matrix matrix) {
        Matrix sum = Matrix.zeros(matrix.rows, 1);
        for (int i = 0; i < matrix.rows; ++i) {
            for (int j = 0; j < matrix.columns; ++j) {
                sum.set(i, 0, sum.get(i, 0) + matrix.get(i, j));
            }
        }
        return sum;
    }

    public Matrix sumCol(Matrix matrix) {
        Matrix sum = Matrix.zeros(1, matrix.columns);
        for (int i = 0; i < matrix.rows; ++i) {
            for (int j = 0; j < matrix.columns; ++j) {
                sum.set(0, j, sum.get(0, j) + matrix.get(i, j));
            }
        }
        return sum;
    }

    public <T> NeuralNetworkDict dictOutputs(T[] array) {

        if (array instanceof int[][]) {
            Map<String, Integer> inputs = new HashMap<>();
            Map<Integer, int[]> outputs = new HashMap<>();
            int index = 0;
            for (int i = 0; i < array.length; i += 1) {
                String key = String.join(",", Arrays.stream((int[]) array[i]).mapToObj(String::valueOf).toArray(String[]::new));
                if (!inputs.containsKey(key)) {
                    inputs.put(key, index);
                    outputs.put(index, (int[]) array[i]);
                    index += 1;
                }
            }
            return new NeuralNetworkDict(inputs, outputs);
        } else if (array instanceof Integer[]) {
            Map<Object, Integer> inputs = new HashMap<>();
            Map<Integer, Object> outputs = new HashMap<>();
            int index = 0;
            for (T t : array) {
                if (!inputs.containsKey(t)) {
                    inputs.put(t, index);
                    outputs.put(index, t);
                    index += 1;
                }
            }
            return new NeuralNetworkDict(inputs, outputs);
        } else {
            Map<Object, Integer> inputs = new HashMap<>();
            Map<Integer, Object> outputs = new HashMap<>();
            int index = 0;
            for (T t : array) {
                if (!inputs.containsKey(t)) {
                    inputs.put(t, index);
                    outputs.put(index, t);
                    index += 1;
                }
            }
            return new NeuralNetworkDict(inputs, outputs);
        }
    }
}

// feedforward-neural-networks activationFunctions.js
interface ActivationFuncation {
    public String getName();

    public int getParamsCount();

    public double activation(double[] value);

    public double derivate(double[] value);
}

class ActivationFuncationTanh implements ActivationFuncation {
    @Override
    public String getName() {
        return "tanh";
    }

    @Override
    public int getParamsCount() {
        return 1;
    }

    @Override
    public double activation(double[] value) {

        return Math.tanh(value[0]);
    }

    @Override
    public double derivate(double[] value) {
        return 1 - value[0] * value[0];
    }
}

class ActivationFuncationIdentity implements ActivationFuncation {
    @Override
    public String getName() {
        return "identity";
    }

    @Override
    public int getParamsCount() {
        return 1;
    }

    @Override
    public double activation(double[] value) {
        return value[0];
    }

    @Override
    public double derivate(double[] value) {
        return 1;
    }
}

class ActivationFuncationLogistic implements ActivationFuncation {
    public double logistic(double val) {
        return 1 / (1 + Math.exp(-val));
    }

    @Override
    public String getName() {
        return "logistic";
    }

    @Override
    public int getParamsCount() {
        return 1;
    }

    @Override
    public double activation(double[] value) {
        return this.logistic(value[0]);

    }

    @Override
    public double derivate(double[] value) {
        return this.logistic(value[0]) * (1 - this.logistic(value[0]));
    }
}

class ActivationFuncationArctan implements ActivationFuncation {
    @Override
    public String getName() {
        return "arctan";
    }

    @Override
    public int getParamsCount() {
        return 1;
    }

    @Override
    public double activation(double[] value) {
        return Math.atan(value[0]);
    }

    @Override
    public double derivate(double[] value) {
        return 1 / (value[0] * value[0] + 1);
    }
}

class ActivationFuncationSoftsign implements ActivationFuncation {
    @Override
    public String getName() {
        return "softsign";
    }

    @Override
    public int getParamsCount() {
        return 1;
    }

    @Override
    public double activation(double[] value) {
        return value[0] / (1 + Math.abs(value[0]));
    }

    @Override
    public double derivate(double[] value) {
        return 1 / ((1 + Math.abs(value[0])) * (1 + Math.abs(value[0])));
    }
}

class ActivationFuncationRelu implements ActivationFuncation {
    @Override
    public String getName() {
        return "relu";
    }

    @Override
    public int getParamsCount() {
        return 1;
    }

    @Override
    public double activation(double[] value) {
        return value[0] < 0 ? 0 : value[0];
    }

    @Override
    public double derivate(double[] value) {
        return value[0] < 0 ? 0 : 1;
    }
}

class ActivationFuncationSoftplus implements ActivationFuncation {
    @Override
    public String getName() {
        return "softplus";
    }

    @Override
    public int getParamsCount() {
        return 1;
    }

    @Override
    public double activation(double[] value) {
        return Math.log(1 + Math.exp(value[0]));
    }

    @Override
    public double derivate(double[] value) {
        return 1 / (1 + Math.exp(-value[0]));
    }
}

class ActivationFuncationBent implements ActivationFuncation {
    final int MLINDEX_BYTES_NUM_2 = 2;

    @Override
    public String getName() {
        return "bent";
    }

    @Override
    public int getParamsCount() {
        return 1;
    }

    @Override
    public double activation(double[] value) {
        return (Math.sqrt(value[0] * value[0] + 1) - 1) / MLINDEX_BYTES_NUM_2 + value[0];
    }

    @Override
    public double derivate(double[] value) {
        return value[0] / (MLINDEX_BYTES_NUM_2 * Math.sqrt(value[0] * value[0] + 1)) + 1;
    }
}

class ActivationFuncationSinusoid implements ActivationFuncation {
    @Override
    public String getName() {
        return "sinusoid";
    }

    @Override
    public int getParamsCount() {
        return 1;
    }

    @Override
    public double activation(double[] value) {
        return Math.sin(value[0]);
    }

    @Override
    public double derivate(double[] value) {
        return Math.cos(value[0]);
    }
}

class ActivationFuncationSinc implements ActivationFuncation {
    @Override
    public String getName() {
        return "sinc";
    }

    @Override
    public int getParamsCount() {
        return 1;
    }

    @Override
    public double activation(double[] value) {
        return value[0] == 0 ? 1 : Math.sin(value[0]) / value[0];
    }

    @Override
    public double derivate(double[] value) {
        return value[0] == 0 ? 0 : Math.cos(value[0]) / value[0] - Math.sin(value[0]) / (value[0] * value[0]);
    }
}

class ActivationFuncationGaussian implements ActivationFuncation {

    @Override
    public String getName() {
        return "gaussian";
    }

    @Override
    public int getParamsCount() {
        return 1;
    }

    @Override
    public double activation(double[] value) {
        return Math.exp(-Math.pow(value[0], value[0]));
    }

    @Override
    public double derivate(double[] value) {
        return MlConstrants.MLINDEX_BYTES_NUM_002 * value[0] * Math.exp(-Math.pow(value[0], value[0]));
    }
}

class ActivationFuncationParametricRelu implements ActivationFuncation {
    final int MLINDEX_BYTES_NUM_2 = 2;

    @Override
    public String getName() {
        return "parametric-relu";
    }

    @Override
    public int getParamsCount() {
        return MLINDEX_BYTES_NUM_2;
    }

    @Override
    public double activation(double[] value) {
        if (value.length > 1) {
            return value[0] < 0 ? value[1] * value[0] : value[0];
        } else {
            return value[0];
        }
    }

    @Override
    public double derivate(double[] value) {
        if (value.length > 1) {
            return value[0] < 0 ? value[1] : 1;
        } else {
            return value[0];
        }
    }
}

class ActivationFuncationExponentialElu implements ActivationFuncation {
    final int MLINDEX_BYTES_NUM_2 = 2;

    public double expElu(double val, double param) {
        return val < 0 ? param * (Math.exp(val) - 1) : val;
    }

    @Override
    public String getName() {
        return "exponential-elu";
    }

    @Override
    public int getParamsCount() {
        return MLINDEX_BYTES_NUM_2;
    }

    @Override
    public double activation(double[] value) {
        return this.expElu(value[0], value[1]);
    }

    @Override
    public double derivate(double[] value) {
        return value[0] < 0 ? this.expElu(value[0], value[1]) + value[1] : 1;
    }
}

class ActivationFuncationSoftExponential implements ActivationFuncation {
    final int MLINDEX_BYTES_NUM_2 = 2;

    public double softExponential(double val, double param) {
        if (param < 0) {
            return -Math.log(1 - param * (val + param)) / param;
        } else if (param > 0) {
            return (Math.exp(param * val) - 1) / param + param;
        } else {
            return val;
        }
    }

    public double softExponentialPrime(double val, double param) {
        if (param < 0) {
            return 1 / (1 - param * (param + val));
        } else {
            return Math.exp(param * val);
        }
    }

    @Override
    public String getName() {
        return "soft-exponential";
    }

    @Override
    public int getParamsCount() {
        return MLINDEX_BYTES_NUM_2;
    }

    @Override
    public double activation(double[] value) {
        return this.softExponential(value[0], value[1]);
    }

    @Override
    public double derivate(double[] value) {
        return this.softExponentialPrime(value[0], value[1]);
    }
}

@FunctionalInterface
interface ActivationFunction<Q, R, T> {
    void apply(Q q, R r, T t);
}

class NeuralNetworkDict<T> {
    public Map<Object, Integer> inputs;
    public Map<Integer, Object> outputs;

    NeuralNetworkDict(Map<Object, Integer> inputs, Map<Integer, Object> outputs) {
        this.inputs = inputs;
        this.outputs = outputs;
    }
}

class FeedforwardNeuralNetwork {
    public int[] hiddenLayers;
    public int iterations;
    public double learningRate;
    public double regularization;
    public NeuralNetworkDict dicts;
    public String activation;
    public double activationParam;
    public List<FeedforwardNeuralNetworksLayer> model = new ArrayList<>();

    FeedforwardNeuralNetwork(MlOptions options) {
        if (options.model != null) {
            this.hiddenLayers = options.hiddenLayers;
            this.iterations = options.iterations;
            this.learningRate = options.learningRate;
            this.regularization = options.regularization;
            this.dicts = options.dicts;
            this.activation = options.activation;
            this.activationParam = options.activationParam;
            for (int i = 0; i < options.layers.size() - 1; ++i) {
                this.model.add(FeedforwardNeuralNetworksLayer.load(options.layers.get(i)));
            }
            this.model.add(new FeedforwardNeuralNetworksOutputLayer(options.layers.get(options.layers.size() - 1)));
        } else {
            this.hiddenLayers = options.hiddenLayers.length == 0 ? new int[]{MlConstrants.MLINDEX_BYTES_NUM_10} : options.hiddenLayers;
            this.iterations = options.iterations == 0 ? MlConstrants.MLINDEX_BYTES_NUM_50 : options.iterations;
            this.learningRate = options.learningRate == 0.0 ? MlConstrants.MLINDEX_BYTES_NUM_001 : options.learningRate;
            this.regularization = options.regularization == null ? MlConstrants.MLINDEX_BYTES_NUM_001 : options.regularization;
            this.activation = options.activation == "" ? "tanh" : options.activation;
            this.activationParam = options.activationParam == 0.0 ? 1.0 : options.activationParam;

            if (!Arrays.asList(MlConstrants.ACTIVATION_FUNCTIONS()).contains(this.activation)) {
                this.activation = "tanh";
            }
        }
    }

    public void buildNetwork(int inputSize, int outputSize) {
        int a = MlConstrants.MLINDEX_BYTES_NUM_2 + (this.hiddenLayers.length - 1);
        this.model = new ArrayList<>();
        MlOptions options = new MlOptions();
        options.inputSize = inputSize;
        options.outputSize = this.hiddenLayers[0];
        options.activation = this.activation;
        options.activationParam = this.activationParam;
        options.regularization = this.regularization;
        options.epsilon = this.learningRate;
        this.model.add(new FeedforwardNeuralNetworksLayer(options));
        for (int i = 1; i < this.hiddenLayers.length; ++i) {
            MlOptions option = new MlOptions();
            option.inputSize = this.hiddenLayers[i - 1];
            option.outputSize = this.hiddenLayers[i];
            option.activation = this.activation;
            option.activationParam = this.activationParam;
            option.regularization = this.regularization;
            option.epsilon = this.learningRate;
            this.model.add(new FeedforwardNeuralNetworksLayer(option));
        }
        MlOptions optionOut = new MlOptions();
        optionOut.inputSize = this.hiddenLayers[hiddenLayers.length - 1];
        optionOut.outputSize = outputSize;
        optionOut.activation = this.activation;
        optionOut.activationParam = this.activationParam;
        optionOut.regularization = this.regularization;
        optionOut.epsilon = this.learningRate;
        this.model.add(new FeedforwardNeuralNetworksOutputLayer(optionOut));
    }

    public <T> void train(Object features, T[] labels) {
        Matrix newFeatures = Matrix.checkMatrix(features);
        this.dicts = MlConstrants.feedforwardNeuralNetworksUtils.dictOutputs(labels);

        int inputSize = newFeatures.columns;

        int outputSize = this.dicts.inputs.size();

        this.buildNetwork(inputSize, outputSize);

        for (int i = 0; i < this.iterations; ++i) {
            Matrix probabilities = this.propagate(newFeatures);
            this.backpropagation(newFeatures, labels, probabilities);
        }
    }

    public Matrix propagate(Matrix x) {
        Matrix input = x;
        for (int i = 0; i < this.model.size(); ++i) {
            input = this.model.get(i).forward(input);
        }
        return input.divColumnVector(MlConstrants.feedforwardNeuralNetworksUtils.sumRow(input));
    }


    public <T> void backpropagation(Matrix features, T[] labels, Matrix probabilities) {
        Matrix newProbabilities = probabilities;
        for (int i = 0; i < newProbabilities.array.length; ++i) {
            Object key = labels[i];
            if (key instanceof Boolean) {
                key = (boolean) key ? 1 : 0;
                newProbabilities.set(i, (Integer) this.dicts.inputs.get(key), newProbabilities.get(i, (Integer) this.dicts.inputs.get(key)) - 1);
            } else {
                if (labels[i] instanceof int[]) {
                    String intKey = String.join(",", Arrays.stream((int[]) labels[i]).mapToObj(String::valueOf).toArray(String[]::new));
                    newProbabilities.set(i, (int) this.dicts.inputs.get(intKey), newProbabilities.get(i, (int) this.dicts.inputs.get(intKey)) - 1);

                } else {
                    newProbabilities.set(i, (int) this.dicts.inputs.get(labels[i]), newProbabilities.get(i, (int) this.dicts.inputs.get(labels[i])) - 1);
                }
            }
        }
        Matrix delta = newProbabilities;
        for (int i = this.model.size() - 1; i >= 0; --i) {
            Matrix a = i > 0 ? this.model.get(i - 1).a : features;
            delta = this.model.get(i).backpropagation(delta, a);
        }

        for (int i = 0; i < this.model.size(); ++i) {
            this.model.get(i).update();
        }
    }

    public List<Object> predict(Object features) {
        features = Matrix.checkMatrix(features);
        List<Object> outputs = new ArrayList<>();
        Matrix probabilities = this.propagate((Matrix) features);
        for (int i = 0; i < ((Matrix) features).rows; i++) {
            Object result = this.dicts.outputs.get(probabilities.maxRowIndex(i)[1]);
            outputs.add(result);
        }
        return outputs;
    }

    public MlOptions toJSON() {
        MlOptions model = new MlOptions();
        model.model = "FNN";
        model.hiddenLayers = this.hiddenLayers;
        model.iterations = this.iterations;
        model.learningRate = this.learningRate;
        model.regularization = this.regularization;
        model.activation = this.activation;
        model.activationParam = this.activationParam;
        model.dicts = this.dicts;
        model.layers = new ArrayList<>();
        for (int i = 0; i < this.model.size(); i++) {
            model.layers.add(this.model.get(i).toJSON());
        }
        return model;
    }

    public static FeedforwardNeuralNetwork load(MlOptions model) {
        if (!model.model.equals("FNN")) {
            throw new RangeError("the current model is not a feed forward network");
        }
        return new FeedforwardNeuralNetwork(model);
    }

}

// feedforward-neural-networks Layer.js
class FeedforwardNeuralNetworksLayer {
    public static final int MLINDEX_BYTES_NUM_2 = 2;
    public int inputSize;
    public int outputSize;
    public double regularization;
    public double epsilon;
    public String activation;
    public double activationParam;
    public Matrix w;

    public Matrix b;
    public Matrix a;
    public Matrix dw;
    public Matrix db;
    public ActivationFunction<Matrix, Integer, Integer> activationFunction;
    public ActivationFunction<Matrix, Integer, Integer> derivate;

    FeedforwardNeuralNetworksLayer(MlOptions options) {
        this.inputSize = options.inputSize;
        this.outputSize = options.outputSize;
        this.regularization = options.regularization;
        this.epsilon = options.epsilon;
        this.activation = String.valueOf(options.activation);
        this.activationParam = options.activationParam;

        ArrayList<ActivationFuncation> activationFunctions = MlConstrants.ACTIVATION_FUNCTIONS();
        String activationName = options.activation;
        Optional<ActivationFuncation> selectedFunction = activationFunctions.stream().filter(activationFuncation -> activationFuncation.getName().equals(activationName)).findFirst();
        selectedFunction.orElse(null);


        Function<Double, Double> actFunction;

        if (selectedFunction.get().getParamsCount() == MLINDEX_BYTES_NUM_2) {
            actFunction = (val) -> {
                if (this.activationParam > 0) {
                    return selectedFunction.get().activation(new double[]{val, this.activationParam});
                } else {
                    return selectedFunction.get().activation(new double[]{val, 1});
                }
            };
        } else {
            actFunction = (val) -> {
                return selectedFunction.get().activation(new double[]{val});
            };
        }
        this.activationFunction = (Matrix matrix, Integer i, Integer j) -> {
            matrix.set(i, j, actFunction.apply(matrix.get(i, j)));
        };


        Function<Double, Double> derFunction;
        if (selectedFunction.get().getParamsCount() == MLINDEX_BYTES_NUM_2) {
            derFunction = (val) -> {
                return selectedFunction.get().derivate(new double[]{val, this.activationParam});
            };
        } else {
            derFunction = (val) -> {
                return selectedFunction.get().derivate(new double[]{val});
            };

        }

        this.derivate = (Matrix matrix, Integer i, Integer j) -> {
            matrix.set(i, j, derFunction.apply(matrix.get(i, j)));
        };

        if (options.model != null) {
            this.w = Matrix.checkMatrix(options.w);
            this.b = Matrix.checkMatrix(options.b);
        } else {
            this.w = Matrix.rand(this.inputSize, this.outputSize, null);
            this.b = Matrix.zeros(1, this.outputSize);
            this.w.apply((Matrix matrix, Integer i, Integer j) -> {
                matrix.set(i, j, matrix.get(i, j) / Math.sqrt(options.inputSize));
            });

        }
    }

    public Matrix forward(Matrix x) {
        Matrix z = x.mmul(this.w).addRowVector(this.b);
        z.apply(this.activationFunction);
        this.a = z.clone();
        return z;
    }

    public Matrix backpropagation(Matrix delta, Matrix a) {
        this.dw = a.transposeView().mmul(delta);
        this.db = MlConstrants.feedforwardNeuralNetworksUtils.sumCol(delta);
        Matrix aCopy = a.clone();
        return delta.mmul(this.w.transposeView()).mul(aCopy.apply(this.derivate));
    }

    public void update() {
        this.dw.add(this.w.clone().mul(this.regularization));
        this.w.add(this.dw.mul(-this.epsilon));
        this.b.add(this.db.mul(-this.epsilon));
    }

    public MlOptions toJSON() {
        MlOptions model = new MlOptions();
        model.model = "Layer";
        model.inputSize = this.inputSize;
        model.outputSize = this.outputSize;
        model.regularization = this.regularization;
        model.epsilon = this.epsilon;
        model.activation = this.activation;
        model.w = this.w;
        model.b = this.b;
        return model;
    }

    public static FeedforwardNeuralNetworksLayer load(MlOptions model) {
        if (!model.model.equals("Layer")) {
            throw new RangeError("the current model is not a Layer model");
        }
        return new FeedforwardNeuralNetworksLayer(model);
    }

}

// feedforward-neural-networks OutputLayer.js
class FeedforwardNeuralNetworksOutputLayer extends FeedforwardNeuralNetworksLayer {

    public FeedforwardNeuralNetworksOutputLayer(MlOptions options) {
        super(options);
        this.activationFunction = (Matrix matrix, Integer i, Integer j) -> {
            matrix.set(i, j, Math.exp(matrix.get(i, j)));
        };

    }
    public static FeedforwardNeuralNetworksOutputLayer load1(MlOptions model) {
        if (!model.model.equals("Layer")) {
            throw new RangeError("the current model is not a Layer model");
        }
        return new FeedforwardNeuralNetworksOutputLayer(model);
    }
}

/*
 * @State
 * @Tags Jetstream2
 */
class Benchmark {

    public static void main(String[] args) {
        runIteration();
    }

    public static void it(String name, Runnable f) {
        f.run();
    }

    public static void run() {
        List<Object> result = new ArrayList<>();
        ArrayList<ActivationFuncation> functions = MlConstrants.ACTIVATION_FUNCTIONS();

        Benchmark.it("Training the neural network with XOR operator", () -> {
            double[][] source = {
                    {0.0, 0.0},
                    {0.0, 1.0},
                    {1.0, 0.0},
                    {1.0, 1.0}
            };
            Matrix trainingSet = new Matrix(source, null);
            Integer[] predictions = {0, 1, 1, 0};
            for (int i = 0; i < functions.size(); i++) {
                MlOptions options = new MlOptions();
                options.hiddenLayers = new int[]{MlConstrants.MLINDEX_BYTES_NUM_4};
                options.iterations = MlConstrants.MLINDEX_BYTES_NUM_040;
                options.learningRate = MlConstrants.MLINDEX_BYTES_NUM_03;
                options.activation = functions.get(i).getName();
                FeedforwardNeuralNetwork xorNn = new FeedforwardNeuralNetwork(options);
                xorNn.train(trainingSet, predictions);
                xorNn.predict(trainingSet);
                if (i == functions.size() - 1) {
                    result.addAll(xorNn.predict(trainingSet));
                }
            }
//            System.out.println("XOR = " + result);

        });
        Benchmark.it("Training the neural network with AND operator", () -> {
            double[][] trainingSet = {
                    {0, 0},
                    {0, 1},
                    {1, 0},
                    {1, 1}
            };
            int[][] predictions = {
                    {1, 0},
                    {1, 0},
                    {1, 0},
                    {0, 1}
            };
            for (int i = 0; i < functions.size(); i++) {
                MlOptions options = new MlOptions();
                options.hiddenLayers = new int[]{MlConstrants.MLINDEX_BYTES_NUM_3};
                options.iterations = MlConstrants.MLINDEX_BYTES_NUM_75;
                options.learningRate = MlConstrants.MLINDEX_BYTES_NUM_03;
                options.activation = functions.get(i).getName();
                FeedforwardNeuralNetwork andNn = new FeedforwardNeuralNetwork(options);
                andNn.train(trainingSet, predictions);
                andNn.predict(trainingSet);
                if (i == functions.size() - 1) {
                    result.clear();
                    result.addAll(andNn.predict(trainingSet));
                }
            }
//            System.out.println("AND = " + Arrays.deepToString(result.toArray()));
        });
        Benchmark.it("Export and import", () -> {
            double[][] trainingSet = {
                    {0, 0},
                    {0, 1},
                    {1, 0},
                    {1, 1}
            };
            Integer[] predictions = {0, 1, 1, 1};
            for (int i = 0; i < functions.size(); i++) {
                MlOptions options = new MlOptions();
                options.hiddenLayers = new int[]{MlConstrants.MLINDEX_BYTES_NUM_4};
                options.iterations = MlConstrants.MLINDEX_BYTES_NUM_040;
                options.learningRate = MlConstrants.MLINDEX_BYTES_NUM_03;
                options.activation = functions.get(i).getName();
                FeedforwardNeuralNetwork nn = new FeedforwardNeuralNetwork(options);
                nn.train(trainingSet, predictions);
                MlOptions model = nn.toJSON();

                FeedforwardNeuralNetwork networkNn = FeedforwardNeuralNetwork.load(model);
                networkNn.predict(trainingSet);
                if (i == functions.size() - 1) {
                    result.clear();
                    result.addAll(networkNn.predict(trainingSet));
                }

            }
//            System.out.println("import = " + result);

        });
        Benchmark.it("Multiclass clasification", () -> {
            double[][] trainingSet = {
                    {0, 0},
                    {0, 1},
                    {1, 0},
                    {1, 1}
            };
            Integer[] predictions = {MlConstrants.MLINDEX_BYTES_NUM_2, 0, 1, 0};
            for (int i = 0; i < functions.size(); i++) {
                MlOptions options = new MlOptions();
                options.hiddenLayers = new int[]{MlConstrants.MLINDEX_BYTES_NUM_4};
                options.iterations = MlConstrants.MLINDEX_BYTES_NUM_040;
                options.learningRate = MlConstrants.MLINDEX_BYTES_NUM_05;
                options.activation = functions.get(i).getName();
                FeedforwardNeuralNetwork nn = new FeedforwardNeuralNetwork(options);
                nn.train(trainingSet, predictions);
                nn.predict(trainingSet);
                if (i == functions.size() - 1) {
                    result.clear();
                    result.addAll(nn.predict(trainingSet));
                }
            }
//            System.out.println("clasification = " + result);

        });
        Benchmark.it("Big case", () -> {
            double[][] trainingSet = {
                    {1, 1},
                    {1, MlConstrants.MLINDEX_BYTES_NUM_2},
                    {MlConstrants.MLINDEX_BYTES_NUM_2, 1},
                    {MlConstrants.MLINDEX_BYTES_NUM_2, MlConstrants.MLINDEX_BYTES_NUM_2},
                    {MlConstrants.MLINDEX_BYTES_NUM_3, 1},
                    {1, MlConstrants.MLINDEX_BYTES_NUM_3},
                    {1, MlConstrants.MLINDEX_BYTES_NUM_4},
                    {MlConstrants.MLINDEX_BYTES_NUM_4, 1},
                    {MlConstrants.MLINDEX_BYTES_NUM_6, 1},
                    {MlConstrants.MLINDEX_BYTES_NUM_6, MlConstrants.MLINDEX_BYTES_NUM_2},
                    {MlConstrants.MLINDEX_BYTES_NUM_6, MlConstrants.MLINDEX_BYTES_NUM_3},
                    {MlConstrants.MLINDEX_BYTES_NUM_6, MlConstrants.MLINDEX_BYTES_NUM_4},
                    {MlConstrants.MLINDEX_BYTES_NUM_6, MlConstrants.MLINDEX_BYTES_NUM_5},
                    {MlConstrants.MLINDEX_BYTES_NUM_5, MlConstrants.MLINDEX_BYTES_NUM_5},
                    {MlConstrants.MLINDEX_BYTES_NUM_4, MlConstrants.MLINDEX_BYTES_NUM_5},
                    {MlConstrants.MLINDEX_BYTES_NUM_3, MlConstrants.MLINDEX_BYTES_NUM_5}
            };
            Integer[][] predictions = {
                    {1, 0},
                    {1, 0},
                    {1, 0},
                    {1, 0},
                    {1, 0},
                    {1, 0},
                    {1, 0},
                    {1, 0},
                    {0, 1},
                    {0, 1},
                    {0, 1},
                    {0, 1},
                    {0, 1},
                    {0, 1},
                    {0, 1},
                    {0, 1}
            };
            for (int i = 0; i < functions.size(); i++) {
                MlOptions options = new MlOptions();
                options.hiddenLayers = new int[]{MlConstrants.MLINDEX_BYTES_NUM_020};
                options.iterations = MlConstrants.NUM_TIME_LOOP_CONST;
                options.learningRate = MlConstrants.MLINDEX_BYTES_NUM_001;
                options.activation = functions.get(i).getName();
                FeedforwardNeuralNetwork nn = new FeedforwardNeuralNetwork(options);
                nn.train(trainingSet, predictions);
                nn.predict(new double[][]{{MlConstrants.MLINDEX_BYTES_NUM_050, MlConstrants.MLINDEX_BYTES_NUM_40}});
                if (i == functions.size() - 1) {
                    result.clear();
                    result.addAll(nn.predict(new double[][]{{MlConstrants.MLINDEX_BYTES_NUM_050, MlConstrants.MLINDEX_BYTES_NUM_40}}));
                }
            }
//            System.out.println("big = " + Arrays.deepToString(result.toArray()));
        });
    }

    /*
     * @Beanchmark
     */
    public static void runIteration() {
        double start = System.nanoTime();
        for (int i = 0; i < MlConstrants.NUM_TIME_LOOP_5; i++) {
            run();
        }
        double end = System.nanoTime();
        System.out.println("ml: ms = " + (end - start) / 1000000);
    }
}
